diff --git a/src/lib/agents/agentConfig.js b/src/lib/agents/agentConfig.ts similarity index 100% rename from src/lib/agents/agentConfig.js rename to src/lib/agents/agentConfig.ts diff --git a/src/lib/agents/agentFields.js b/src/lib/agents/agentFields.ts similarity index 98% rename from src/lib/agents/agentFields.js rename to src/lib/agents/agentFields.ts index a19ca05..51a212b 100644 --- a/src/lib/agents/agentFields.js +++ b/src/lib/agents/agentFields.ts @@ -90,8 +90,8 @@ const listOrRemove = (list) => (list && list.length ? list : REMOVE); * only knows about four fields cannot erase the other ten, and a definition * hand-written with comments and key order survives being saved from the form. */ -export function agentPatch(fields = {}) { - const patch = {}; +export function agentPatch(fields: any = {}) { + const patch: any = {}; const scalar = (key, value) => { if (value === undefined) return; @@ -198,7 +198,7 @@ function writeSection(body, heading, text) { * therefore never has to remember which half a given field lives in — which is * exactly the mistake that made instructions silently unsaveable. */ -export function applyAgentFields(source, fields = {}) { +export function applyAgentFields(source, fields: any = {}) { const patched = patchFrontmatter(source, agentPatch(fields)); if (fields.instructions === undefined) return patched; diff --git a/src/lib/agents/agentLifecycle.js b/src/lib/agents/agentLifecycle.ts similarity index 98% rename from src/lib/agents/agentLifecycle.js rename to src/lib/agents/agentLifecycle.ts index 1bcb0ce..75e7352 100644 --- a/src/lib/agents/agentLifecycle.js +++ b/src/lib/agents/agentLifecycle.ts @@ -63,7 +63,9 @@ export const restoreAgent = (source) => * copy into the switcher the moment it was made. */ /** @param {string} source @param {any} [options] */ -export function duplicateAgent(source, { name, existingIds = [] } = {}) { +export function duplicateAgent(source, { name, existingIds = [] }: { + name?: any; existingIds?: string[]; +} = {}) { const agent = parseAgent(source, { custom: true }); const copyName = name || `${agent.name} copy`; diff --git a/src/lib/agents/agentStore.js b/src/lib/agents/agentStore.ts similarity index 98% rename from src/lib/agents/agentStore.js rename to src/lib/agents/agentStore.ts index dd1d0c0..22e3842 100644 --- a/src/lib/agents/agentStore.js +++ b/src/lib/agents/agentStore.ts @@ -67,7 +67,9 @@ export function sourcesFrom(rows, shippedIds = new Set()) { export function useSaveAgentDefinition() { const qc = useQueryClient(); return useMutation({ - mutationFn: async ({ definitionId, markdown, visibility = 'personal' }) => { + mutationFn: async ({ definitionId, markdown, visibility = 'personal' }: { + definitionId: string; markdown: string; visibility?: string; + }) => { const rows = qc.getQueryData(KEY) || (await base44.entities.AgentDefinition.list('-created_date', 200)); const existing = (rows || []).find((r) => r.definition_id === definitionId); /* visibility is immutable after creation, so it is sent only on create — @@ -85,7 +87,7 @@ export function useSaveAgentDefinition() { export function useDeleteAgentDefinition() { const qc = useQueryClient(); return useMutation({ - mutationFn: async (definitionId) => { + mutationFn: async (definitionId: string) => { const rows = qc.getQueryData(KEY) || (await base44.entities.AgentDefinition.list('-created_date', 200)); const existing = (rows || []).find((r) => r.definition_id === definitionId); if (!existing) return { ok: true }; diff --git a/src/lib/agents/capabilityTest.js b/src/lib/agents/capabilityTest.ts similarity index 100% rename from src/lib/agents/capabilityTest.js rename to src/lib/agents/capabilityTest.ts diff --git a/src/lib/agents/context.js b/src/lib/agents/context.ts similarity index 100% rename from src/lib/agents/context.js rename to src/lib/agents/context.ts diff --git a/src/lib/agents/conversationInsights.js b/src/lib/agents/conversationInsights.ts similarity index 100% rename from src/lib/agents/conversationInsights.js rename to src/lib/agents/conversationInsights.ts diff --git a/src/lib/agents/customAgents.js b/src/lib/agents/customAgents.ts similarity index 100% rename from src/lib/agents/customAgents.js rename to src/lib/agents/customAgents.ts diff --git a/src/lib/agents/knowledge.js b/src/lib/agents/knowledge.ts similarity index 100% rename from src/lib/agents/knowledge.js rename to src/lib/agents/knowledge.ts diff --git a/src/lib/agents/registry.js b/src/lib/agents/registry.ts similarity index 100% rename from src/lib/agents/registry.js rename to src/lib/agents/registry.ts diff --git a/src/lib/agents/runtime.js b/src/lib/agents/runtime.ts similarity index 99% rename from src/lib/agents/runtime.js rename to src/lib/agents/runtime.ts index 0947b95..ed021d1 100644 --- a/src/lib/agents/runtime.js +++ b/src/lib/agents/runtime.ts @@ -61,6 +61,7 @@ import { reasoningFor } from './vocabulary'; * read. Removing the parameter would be a wider edit for no behavioural gain, * and agentScopedDisabledWith exists precisely to pass it. */ +export function agentSkillIds(agent: any, agents?: any): any[]; export function agentSkillIds(agent) { if (!agent) return []; return [...new Set(agent.skills || [])]; diff --git a/src/lib/agents/useAgents.js b/src/lib/agents/useAgents.ts similarity index 80% rename from src/lib/agents/useAgents.js rename to src/lib/agents/useAgents.ts index 9274f92..bf119f0 100644 --- a/src/lib/agents/useAgents.js +++ b/src/lib/agents/useAgents.ts @@ -9,6 +9,37 @@ import { AGENTS, parseAgent, readAgentRegistry, validateAgentSource } from './re import { customAgentSource } from './customAgents'; import { archiveAgent, duplicateAgent, publishAgent, restoreAgent } from './agentLifecycle'; +/** + * What every action on this hook resolves to. + * + * Five shapes across eleven `return` statements: `{ ok: false, error }`, + * `{ ok: false, conflict }`, `{ ok: true, agent }`, a bare `{ ok: true }`, and + * `duplicate`'s `{ ...result, id }`, which spreads a success and adds the id of + * the copy it made. + * + * This is written open — `ok` plus three optional fields — rather than as the + * discriminated union it wants to be, and that is a deliberate limit of this + * phase rather than a judgement that the open version is right. + * + * A proper union would make `result.agent` unreadable until `result.ok` had + * been checked, and `result.conflict` unreadable on a success. As written, + * nothing stops a caller reading `.agent` off a failure and getting + * `undefined`. Closing it needs `as const` on ten literals so `ok` stops + * widening to `boolean` and starts discriminating — edits to the bodies of + * these functions, which a rename is not allowed to make. + * + * Recorded here so the follow-up is a decision someone takes, not something + * that has to be rediscovered. + */ +export interface AgentActionResult { + ok: boolean; + error?: any; + conflict?: any; + agent?: any; + /** Only `duplicate` sets this: the id of the copy. */ + id?: any; +} + /** * Reading and writing agents, in one place. * @@ -91,7 +122,7 @@ export function useAgents() { * definition that cannot be read is an agent that silently stops existing. * Returns `{ ok, error }` so a form can stay on screen and say why. */ - const save = useCallback(async (/** @type {string} */ source, /** @type {any} */ { message } = {}) => { + const save = useCallback(async (source: string, { message }: any = {}): Promise => { const problem = validateAgentSource(source); if (problem) return { ok: false, error: problem }; @@ -109,7 +140,7 @@ export function useAgents() { }, [saveDefinition]); /** Removes the account's definition. A shipped agent returns to its shipped form. */ - const remove = useCallback(async (id) => { + const remove = useCallback(async (id): Promise => { try { await deleteDefinition.mutateAsync(id); } catch (error) { @@ -125,7 +156,7 @@ export function useAgents() { * Returns `{ ok: false, conflict }` when the stored definition has moved on, * so the screen can say what would be lost instead of losing it. */ - const publish = useCallback(async (id) => { + const publish = useCallback(async (id): Promise => { const source = sourceFor(id); if (!source) return { ok: false, error: 'That agent has no definition to publish.' }; @@ -138,19 +169,19 @@ export function useAgents() { return save(result.source, { message: `${live?.name || id} published` }); }, [sourceFor, agents, save]); - const archive = useCallback(async (id) => { + const archive = useCallback(async (id): Promise => { const source = sourceFor(id); if (!source) return { ok: false, error: 'That agent has no definition to archive.' }; return save(archiveAgent(source), { message: `${agents.find((a) => a.id === id)?.name || id} archived` }); }, [sourceFor, agents, save]); - const restore = useCallback(async (id) => { + const restore = useCallback(async (id): Promise => { const source = sourceFor(id); if (!source) return { ok: false, error: 'That agent has no definition to restore.' }; return save(restoreAgent(source), { message: 'Restored as a draft' }); }, [sourceFor, save]); - const duplicate = useCallback(async (id) => { + const duplicate = useCallback(async (id): Promise => { const source = sourceFor(id); if (!source) return { ok: false, error: 'That agent has no definition to copy.' }; const copy = duplicateAgent(source, { existingIds: agents.map((a) => a.id) }); diff --git a/src/lib/agents/vocabulary.js b/src/lib/agents/vocabulary.ts similarity index 100% rename from src/lib/agents/vocabulary.js rename to src/lib/agents/vocabulary.ts