Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2259b6895f | ||
|
|
2df1bf83f4 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cobble",
|
"name": "cobble",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export default async function grabToolsOverview({ user_id }: Params): Promise<Cu
|
|||||||
status: tool.status,
|
status: tool.status,
|
||||||
last_error: tool.last_error,
|
last_error: tool.last_error,
|
||||||
last_checked_at: tool.last_checked_at,
|
last_checked_at: tool.last_checked_at,
|
||||||
|
updated_at: tool.updated_at,
|
||||||
access: { all_bots: tool.all_bots, ...access(tool.id) },
|
access: { all_bots: tool.all_bots, ...access(tool.id) },
|
||||||
...(targets.can_edit ? { parameters: tool.parameters, config: tool.config } : {}),
|
...(targets.can_edit ? { parameters: tool.parameters, config: tool.config } : {}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export default function parseCustomToolRow({ row }: Params): CustomToolRecord {
|
|||||||
status: (row.status || "untested") as CustomToolStatus,
|
status: (row.status || "untested") as CustomToolStatus,
|
||||||
last_error: row.last_error || "",
|
last_error: row.last_error || "",
|
||||||
last_checked_at: typeof row.last_checked_at == "number" ? row.last_checked_at : null,
|
last_checked_at: typeof row.last_checked_at == "number" ? row.last_checked_at : null,
|
||||||
|
updated_at: typeof row.updated_at == "number" ? row.updated_at : null,
|
||||||
kind: row.kind || "prompt",
|
kind: row.kind || "prompt",
|
||||||
config: parseJsonColumn<Record<string, unknown>>({ text: row.config, fallback: {} }),
|
config: parseJsonColumn<Record<string, unknown>>({ text: row.config, fallback: {} }),
|
||||||
} as CustomToolRecord;
|
} as CustomToolRecord;
|
||||||
|
|||||||
@@ -29,5 +29,6 @@ export default function parseMcpServerRow({ row }: Params): McpServerRecord {
|
|||||||
status: (row.status || "untested") as McpServerStatus,
|
status: (row.status || "untested") as McpServerStatus,
|
||||||
last_error: row.last_error || "",
|
last_error: row.last_error || "",
|
||||||
last_synced_at: typeof row.last_synced_at == "number" ? row.last_synced_at : null,
|
last_synced_at: typeof row.last_synced_at == "number" ? row.last_synced_at : null,
|
||||||
|
updated_at: typeof row.updated_at == "number" ? row.updated_at : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,5 +17,6 @@ export default function parseSkillRow({ row }: Params): SkillRecord {
|
|||||||
body: row.body || "",
|
body: row.body || "",
|
||||||
all_bots: row.all_bots == 1,
|
all_bots: row.all_bots == 1,
|
||||||
enabled: row.enabled != 0,
|
enabled: row.enabled != 0,
|
||||||
|
updated_at: typeof row.updated_at == "number" ? row.updated_at : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,12 @@ type Params = {
|
|||||||
/**
|
/**
|
||||||
* Saves how a custom tool's run went (`status`, `last_error`,
|
* Saves how a custom tool's run went (`status`, `last_error`,
|
||||||
* `last_checked_at`), for its row in Settings, under Tools. Never throws:
|
* `last_checked_at`), for its row in Settings, under Tools. Never throws:
|
||||||
* a run doesn't fail over its bookkeeping.
|
* a run doesn't fail over its bookkeeping. `updated_at` is left alone: it
|
||||||
|
* says when the tool's settings last changed.
|
||||||
*/
|
*/
|
||||||
export default async function recordCustomToolRun({ custom_tool_id, error }: Params) {
|
export default async function recordCustomToolRun({ custom_tool_id, error }: Params) {
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
await BunSQLite.sql({
|
await BunSQLite.sql({
|
||||||
sql: `UPDATE custom_tools SET status = ?, last_error = ?, last_checked_at = ?, updated_at = ? WHERE id = ?`,
|
sql: `UPDATE custom_tools SET status = ?, last_error = ?, last_checked_at = ? WHERE id = ?`,
|
||||||
values: [error ? "error" : "ok", (error || "").slice(0, 4000), now, now, custom_tool_id],
|
values: [error ? "error" : "ok", (error || "").slice(0, 4000), Date.now(), custom_tool_id],
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,12 @@ type Params = {
|
|||||||
/**
|
/**
|
||||||
* Saves whether a bot's run could connect to an MCP server, so its row in
|
* Saves whether a bot's run could connect to an MCP server, so its row in
|
||||||
* Settings, under MCPs, shows a server that stopped answering (or came
|
* Settings, under MCPs, shows a server that stopped answering (or came
|
||||||
* back). Its tools listing is left as it was. Never throws.
|
* back). Its tools listing and `updated_at` (when its settings last
|
||||||
|
* changed) are left as they were. Never throws.
|
||||||
*/
|
*/
|
||||||
export default async function recordMcpServerConnection({ mcp_server_id, error }: Params) {
|
export default async function recordMcpServerConnection({ mcp_server_id, error }: Params) {
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
await BunSQLite.sql({
|
await BunSQLite.sql({
|
||||||
sql: `UPDATE mcp_servers SET status = ?, last_error = ?, last_synced_at = ?, updated_at = ? WHERE id = ?`,
|
sql: `UPDATE mcp_servers SET status = ?, last_error = ?, last_synced_at = ? WHERE id = ?`,
|
||||||
values: [error ? "error" : "ok", (error || "").slice(0, 4000), now, now, mcp_server_id],
|
values: [error ? "error" : "ok", (error || "").slice(0, 4000), Date.now(), mcp_server_id],
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ type Params = {
|
|||||||
* super admins and admins. An admin never sees the coordinator, so its
|
* super admins and admins. An admin never sees the coordinator, so its
|
||||||
* attachment is kept as it is; only a super admin sets "every bot", which
|
* attachment is kept as it is; only a super admin sets "every bot", which
|
||||||
* when turned on gives it to every bot (`attachToAllBots`) and every new
|
* when turned on gives it to every bot (`attachToAllBots`) and every new
|
||||||
* one. Turning it off takes it from no one.
|
* one. Turning it off takes it from no one. Either way it counts as a
|
||||||
|
* change to the extension (`updated_at`).
|
||||||
*/
|
*/
|
||||||
export default async function setExtensionAccess({ user_id, kind, id, bot_ids, workspace_ids, all_bots }: Params) {
|
export default async function setExtensionAccess({ user_id, kind, id, bot_ids, workspace_ids, all_bots }: Params) {
|
||||||
const role = await assertCrewManager({ user_id });
|
const role = await assertCrewManager({ user_id });
|
||||||
@@ -63,5 +64,7 @@ export default async function setExtensionAccess({ user_id, kind, id, bot_ids, w
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (all_bots && found.payload?.[0]?.all_bots != 1) await attachToAllBots({ kind, id });
|
if (all_bots && found.payload?.[0]?.all_bots != 1) await attachToAllBots({ kind, id });
|
||||||
|
} else {
|
||||||
|
await BunSQLite.sql({ sql: `UPDATE ${table} SET updated_at = ? WHERE id = ?`, values: [now, id] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export default async function syncMcpServer({ mcp_server_id, reinstall }: Params
|
|||||||
const tools = await listMcpTools({ client: await open.client() });
|
const tools = await listMcpTools({ client: await open.client() });
|
||||||
|
|
||||||
await BunSQLite.sql({
|
await BunSQLite.sql({
|
||||||
sql: `UPDATE mcp_servers SET tools_cache = ?, status = 'ok', last_error = '', last_synced_at = ?, updated_at = ? WHERE id = ?`,
|
sql: `UPDATE mcp_servers SET tools_cache = ?, status = 'ok', last_error = '', last_synced_at = ? WHERE id = ?`,
|
||||||
values: [JSON.stringify(tools), now, now, server.id],
|
values: [JSON.stringify(tools), now, server.id],
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ok: true as const, tools };
|
return { ok: true as const, tools };
|
||||||
@@ -49,8 +49,8 @@ export default async function syncMcpServer({ mcp_server_id, reinstall }: Params
|
|||||||
}).slice(0, 4000);
|
}).slice(0, 4000);
|
||||||
|
|
||||||
await BunSQLite.sql({
|
await BunSQLite.sql({
|
||||||
sql: `UPDATE mcp_servers SET status = 'error', last_error = ?, last_synced_at = ?, updated_at = ? WHERE id = ?`,
|
sql: `UPDATE mcp_servers SET status = 'error', last_error = ?, last_synced_at = ? WHERE id = ?`,
|
||||||
values: [message, now, now, server.id],
|
values: [message, now, server.id],
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ok: false as const, error: message };
|
return { ok: false as const, error: message };
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export default async function testMcpDraft({
|
|||||||
status: "untested",
|
status: "untested",
|
||||||
last_error: "",
|
last_error: "",
|
||||||
last_synced_at: null,
|
last_synced_at: null,
|
||||||
|
updated_at: null,
|
||||||
},
|
},
|
||||||
envs,
|
envs,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const weather: CustomToolRecord = {
|
|||||||
status: "ok",
|
status: "ok",
|
||||||
last_error: "",
|
last_error: "",
|
||||||
last_checked_at: null,
|
last_checked_at: null,
|
||||||
|
updated_at: null,
|
||||||
kind: "http",
|
kind: "http",
|
||||||
config: {
|
config: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ function skill(overrides: Partial<SkillRecord> & { slug: string }): SkillRecord
|
|||||||
body: `${overrides.slug} body`,
|
body: `${overrides.slug} body`,
|
||||||
all_bots: false,
|
all_bots: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
updated_at: null,
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import checkedAgo from "./checked-ago";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
at: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When an extension's settings last changed, for a row's subtitle:
|
||||||
|
* "Updated 5m ago". Nothing when it's never been stamped.
|
||||||
|
*/
|
||||||
|
export default function updatedAgo({ at }: Params) {
|
||||||
|
return at ? `Updated ${checkedAgo({ at })}` : undefined;
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import SettingsListRow from "../../(partials)/settings-list-row";
|
|||||||
import SettingsRowAction from "../../(partials)/settings-row-action";
|
import SettingsRowAction from "../../(partials)/settings-row-action";
|
||||||
import checkedAgo from "../../(utils)/checked-ago";
|
import checkedAgo from "../../(utils)/checked-ago";
|
||||||
import extensionAccessLabel from "../../(utils)/extension-access-label";
|
import extensionAccessLabel from "../../(utils)/extension-access-label";
|
||||||
|
import updatedAgo from "../../(utils)/updated-ago";
|
||||||
import McpServerModal from "./mcp-server-modal";
|
import McpServerModal from "./mcp-server-modal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -63,6 +64,7 @@ export default function McpServerRow({ server, targets, onChanged, onError }: Pr
|
|||||||
subtitle={[
|
subtitle={[
|
||||||
McpTransports.find((t) => t.value == server.transport)?.title,
|
McpTransports.find((t) => t.value == server.transport)?.title,
|
||||||
extensionAccessLabel({ access: server.access }),
|
extensionAccessLabel({ access: server.access }),
|
||||||
|
updatedAgo({ at: server.updated_at }),
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" · ")}
|
.join(" · ")}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import ExtensionAccessModal from "../../(partials)/extension-access-modal";
|
|||||||
import SettingsListRow from "../../(partials)/settings-list-row";
|
import SettingsListRow from "../../(partials)/settings-list-row";
|
||||||
import SettingsRowAction from "../../(partials)/settings-row-action";
|
import SettingsRowAction from "../../(partials)/settings-row-action";
|
||||||
import extensionAccessLabel from "../../(utils)/extension-access-label";
|
import extensionAccessLabel from "../../(utils)/extension-access-label";
|
||||||
|
import updatedAgo from "../../(utils)/updated-ago";
|
||||||
import SkillModal from "./skill-modal";
|
import SkillModal from "./skill-modal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -51,7 +52,12 @@ export default function SkillRow({ skill, targets, limits, onChanged, onError }:
|
|||||||
avatar={<IconTile Icon={BookOpen} />}
|
avatar={<IconTile Icon={BookOpen} />}
|
||||||
title={skill.name}
|
title={skill.name}
|
||||||
badge={skill.enabled ? undefined : "Off"}
|
badge={skill.enabled ? undefined : "Off"}
|
||||||
subtitle={[skill.slug, extensionAccessLabel({ access: skill.access }), skill.description]
|
subtitle={[
|
||||||
|
skill.slug,
|
||||||
|
extensionAccessLabel({ access: skill.access }),
|
||||||
|
updatedAgo({ at: skill.updated_at }),
|
||||||
|
skill.description,
|
||||||
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" · ")}
|
.join(" · ")}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import SettingsListRow from "../../(partials)/settings-list-row";
|
|||||||
import SettingsRowAction from "../../(partials)/settings-row-action";
|
import SettingsRowAction from "../../(partials)/settings-row-action";
|
||||||
import checkedAgo from "../../(utils)/checked-ago";
|
import checkedAgo from "../../(utils)/checked-ago";
|
||||||
import extensionAccessLabel from "../../(utils)/extension-access-label";
|
import extensionAccessLabel from "../../(utils)/extension-access-label";
|
||||||
|
import updatedAgo from "../../(utils)/updated-ago";
|
||||||
import CustomToolModal from "./custom-tool-modal";
|
import CustomToolModal from "./custom-tool-modal";
|
||||||
import CustomToolTryModal from "./custom-tool-try-modal";
|
import CustomToolTryModal from "./custom-tool-try-modal";
|
||||||
|
|
||||||
@@ -72,6 +73,7 @@ export default function CustomToolRow({ tool, targets, onChanged, onRefreshed, o
|
|||||||
subtitle={[
|
subtitle={[
|
||||||
CustomToolKinds.find((k) => k.value == tool.kind)?.title,
|
CustomToolKinds.find((k) => k.value == tool.kind)?.title,
|
||||||
extensionAccessLabel({ access: tool.access }),
|
extensionAccessLabel({ access: tool.access }),
|
||||||
|
updatedAgo({ at: tool.updated_at }),
|
||||||
tool.description,
|
tool.description,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|||||||
@@ -752,6 +752,10 @@ export type McpServerRecord = {
|
|||||||
status: McpServerStatus;
|
status: McpServerStatus;
|
||||||
last_error: string;
|
last_error: string;
|
||||||
last_synced_at: number | null;
|
last_synced_at: number | null;
|
||||||
|
/**
|
||||||
|
* When its settings last changed (an edit or who gets it), not a run or check.
|
||||||
|
*/
|
||||||
|
updated_at: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CustomToolHttpConfig = {
|
export type CustomToolHttpConfig = {
|
||||||
@@ -785,6 +789,10 @@ export type CustomToolRecord = {
|
|||||||
status: CustomToolStatus;
|
status: CustomToolStatus;
|
||||||
last_error: string;
|
last_error: string;
|
||||||
last_checked_at: number | null;
|
last_checked_at: number | null;
|
||||||
|
/**
|
||||||
|
* When its settings last changed (an edit or who gets it), not a run or check.
|
||||||
|
*/
|
||||||
|
updated_at: number | null;
|
||||||
} & (
|
} & (
|
||||||
| { kind: "http"; config: CustomToolHttpConfig }
|
| { kind: "http"; config: CustomToolHttpConfig }
|
||||||
| { kind: "script"; config: CustomToolScriptConfig }
|
| { kind: "script"; config: CustomToolScriptConfig }
|
||||||
@@ -802,6 +810,10 @@ export type SkillRecord = {
|
|||||||
body: string;
|
body: string;
|
||||||
all_bots: boolean;
|
all_bots: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
/**
|
||||||
|
* When its settings last changed (an edit or who gets it), not a run or check.
|
||||||
|
*/
|
||||||
|
updated_at: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -943,6 +955,7 @@ export type PublicCustomTool = {
|
|||||||
status: CustomToolStatus;
|
status: CustomToolStatus;
|
||||||
last_error: string;
|
last_error: string;
|
||||||
last_checked_at: number | null;
|
last_checked_at: number | null;
|
||||||
|
updated_at: number | null;
|
||||||
access: ExtensionAccess;
|
access: ExtensionAccess;
|
||||||
parameters?: Record<string, unknown>;
|
parameters?: Record<string, unknown>;
|
||||||
config?: CustomToolHttpConfig | CustomToolScriptConfig | CustomToolPromptConfig;
|
config?: CustomToolHttpConfig | CustomToolScriptConfig | CustomToolPromptConfig;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const current: SkillRecord = {
|
|||||||
body: "1. Search the client's rankings.\n2. Write the report.\n3. Attach it.",
|
body: "1. Search the client's rankings.\n2. Write the report.\n3. Attach it.",
|
||||||
all_bots: false,
|
all_bots: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
updated_at: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("describeSkillProposal", () => {
|
describe("describeSkillProposal", () => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const current: SkillRecord = {
|
|||||||
body: "1. Install Baileys.\n2. Pair.",
|
body: "1. Install Baileys.\n2. Pair.",
|
||||||
all_bots: false,
|
all_bots: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
updated_at: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("skillProposalChanges", () => {
|
describe("skillProposalChanges", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user