@ai-agent-forge/plugin-todo-set 0.85.1 → 0.85.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-forge.json +11 -3
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/plugin.json +6 -2
package/agent-forge.json
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"packageFormatVersion": 1,
|
|
3
3
|
"id": "agent-forge.plugin.todo-set",
|
|
4
|
-
"pluginVersion": "0.
|
|
4
|
+
"pluginVersion": "0.85.2",
|
|
5
5
|
"apiVersion": "1",
|
|
6
6
|
"minHostVersion": "0.84.4",
|
|
7
7
|
"platform": {
|
|
8
|
-
"os": [
|
|
9
|
-
|
|
8
|
+
"os": [
|
|
9
|
+
"windows",
|
|
10
|
+
"macos",
|
|
11
|
+
"linux"
|
|
12
|
+
],
|
|
13
|
+
"runtime": [
|
|
14
|
+
{
|
|
15
|
+
"name": "node"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
10
18
|
}
|
|
11
19
|
}
|
package/dist/entry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D;;;;;;;;GAQG;AACH,wBAA8B,wBAAwB,
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D;;;;;;;;GAQG;AACH,wBAA8B,wBAAwB,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC,CAW3G","sourcesContent":["import type { CapabilityAPI } from \"@agent-forge/plugin-sdk\";\nimport { createCapability } from \"./index.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The capability factory returns its registrations as an array, while the\n * manifest loader contract expects `void` or a single `{ dispose() }` instance\n * (`normalizePluginInstance` does not accept arrays). The wrapper collects the\n * registrations and disposes them in reverse order on plugin teardown — the\n * same disposal semantics as the host's embedded `syncRegistrationFactory`.\n */\nexport default async function createTodoSetPluginEntry(api: CapabilityAPI): Promise<{ dispose: () => void }> {\n\tconst registrations = await createCapability(api);\n\treturn {\n\t\tdispose: () => {\n\t\t\tfor (const registration of [...registrations].reverse()) {\n\t\t\t\t// Registration disposal in the capability runtime is synchronous;\n\t\t\t\t// void consumes the declared Promise possibility of the contract.\n\t\t\t\tvoid registration.dispose();\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|
package/dist/entry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,wBAAwB,
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,wBAAwB,CAAC,GAAkB,EAAoC;IAC5G,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAClD,OAAO;QACN,OAAO,EAAE,GAAG,EAAE,CAAC;YACd,KAAK,MAAM,YAAY,IAAI,CAAC,GAAG,aAAa,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;gBACzD,kEAAkE;gBAClE,kEAAkE;gBAClE,KAAK,YAAY,CAAC,OAAO,EAAE,CAAC;YAC7B,CAAC;QAAA,CACD;KACD,CAAC;AAAA,CACF","sourcesContent":["import type { CapabilityAPI } from \"@agent-forge/plugin-sdk\";\nimport { createCapability } from \"./index.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The capability factory returns its registrations as an array, while the\n * manifest loader contract expects `void` or a single `{ dispose() }` instance\n * (`normalizePluginInstance` does not accept arrays). The wrapper collects the\n * registrations and disposes them in reverse order on plugin teardown — the\n * same disposal semantics as the host's embedded `syncRegistrationFactory`.\n */\nexport default async function createTodoSetPluginEntry(api: CapabilityAPI): Promise<{ dispose: () => void }> {\n\tconst registrations = await createCapability(api);\n\treturn {\n\t\tdispose: () => {\n\t\t\tfor (const registration of [...registrations].reverse()) {\n\t\t\t\t// Registration disposal in the capability runtime is synchronous;\n\t\t\t\t// void consumes the declared Promise possibility of the contract.\n\t\t\t\tvoid registration.dispose();\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGrF,MAAM,WAAW,UAAU;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,UAAU,EAAE,CAAC;CACpB;AAoFD,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,aAAa,GAChB,OAAO,CAAC,SAAS,sBAAsB,EAAE,CAAC,GAAG,SAAS,sBAAsB,EAAE,CA4ChF","sourcesContent":["/**\n * Todo-set capability plugin: a self-contained todo state machine, extracted\n * from the host's `src/capabilities/todo-set.ts` (D-075 S4, first B-class\n * batch).\n *\n * Todos persist as append-only `todo.state` session entries and every change\n * is announced as a `todo.changed` event. `planId` is a soft reference: a\n * plain string whose target is checked ADVISORILY against the plan plugin's\n * LATEST `plan.state` snapshot (the same latest-snapshot semantics as this\n * plugin's own {@link loadState}); an unknown id yields a response warning,\n * never a failure (the same #10 pattern as the plan plugin's goalId check).\n * The plugin only consumes the public `session`, `events`, and `tools`\n * capabilities and never writes state owned by another plugin.\n */\n\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\n\nexport interface TodoRecord {\n\tid: string;\n\t/** Soft reference: a plain string; unknown ids yield a response warning, not a failure. */\n\tplanId: string;\n\ttitle: string;\n\tstatus: \"pending\" | \"done\" | \"blocked\";\n\trevision: number;\n\tevidence: string[];\n}\n\nexport interface TodoState {\n\ttodos: TodoRecord[];\n}\n\nconst stateEntry = \"todo.state\";\n/** Session protocol key of the plan plugin's snapshot entry (plugins/plan). */\nconst planStateEntry = \"plan.state\";\n\nconst todoSchema = Type.Object({\n\tid: Type.String({ description: \"Stable id of the todo to create or update\" }),\n\tplanId: Type.String({\n\t\tdescription:\n\t\t\t\"Soft reference to a plan id; an id unknown to this session's plan state yields a response warning, not a failure\",\n\t}),\n\ttitle: Type.String({ description: \"Human-readable todo title\" }),\n\trevision: Type.Integer({\n\t\tdescription: \"New revision counter; must be greater than the stored revision of an existing todo\",\n\t\tminimum: 0,\n\t}),\n\tstatus: Type.Optional(\n\t\tType.Union([Type.Literal(\"pending\"), Type.Literal(\"done\"), Type.Literal(\"blocked\")], {\n\t\t\tdescription: \"Next status; defaults to the stored status for existing todos, otherwise pending\",\n\t\t}),\n\t),\n\tevidence: Type.Optional(\n\t\tType.Array(Type.String(), {\n\t\t\tdescription: \"Evidence references; at least one is required when status is done\",\n\t\t}),\n\t),\n});\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"Todo input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction text(value: unknown, name: string): string {\n\tif (typeof value !== \"string\" || value.trim() === \"\") throw new Error(`${name} must be a non-empty string`);\n\treturn value;\n}\n\nfunction revision(value: unknown): number {\n\tif (!Number.isInteger(value) || (value as number) < 0) throw new Error(\"revision must be a non-negative integer\");\n\treturn value as number;\n}\n\nfunction evidence(value: unknown): string[] {\n\tif (value === undefined) return [];\n\tif (!Array.isArray(value) || value.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new Error(\"evidence must be a string array\");\n\t}\n\treturn [...(value as string[])];\n}\n\nfunction loadState(api: CapabilityAPI): TodoState {\n\tconst entries = api.session?.getBranchEntries() ?? [];\n\tconst entry = [...entries].reverse().find((item) => item.customType === stateEntry);\n\tif (!entry?.data || typeof entry.data !== \"object\") return { todos: [] };\n\tconst data = entry.data as Partial<TodoState>;\n\treturn { todos: Array.isArray(data.todos) ? [...data.todos] : [] };\n}\n\n/**\n * Whether the session branch's LATEST `plan.state` snapshot knows a plan with\n * the given id (the same latest-snapshot semantics as {@link loadState} and\n * the same advisory pattern as the plan plugin's goalKnownToSession). The\n * check is advisory only (#10): a missing plan downgrades to a warning in the\n * tool response — the soft-reference write path never fails on it.\n */\nfunction planKnownToSession(api: CapabilityAPI, planId: string): boolean {\n\tconst entries = api.session?.getBranchEntries() ?? [];\n\tfor (let index = entries.length - 1; index >= 0; index -= 1) {\n\t\tconst entry = entries[index];\n\t\tif (entry.customType !== planStateEntry) continue;\n\t\tconst data = entry.data;\n\t\tif (!data || typeof data !== \"object\" || Array.isArray(data)) return false;\n\t\tconst plans = (data as { plans?: unknown }).plans;\n\t\tif (!Array.isArray(plans)) return false;\n\t\treturn plans.some(\n\t\t\t(plan) => plan !== null && typeof plan === \"object\" && (plan as { id?: unknown }).id === planId,\n\t\t);\n\t}\n\treturn false;\n}\n\nexport function createCapability(\n\tapi: CapabilityAPI,\n): Promise<readonly DisposableRegistration[]> | readonly DisposableRegistration[] {\n\tlet state = loadState(api);\n\n\tconst persist = async (): Promise<TodoState> => {\n\t\tapi.session?.appendEntry(stateEntry, state);\n\t\tawait api.publish({ type: \"todo.changed\", version: 1, correlationId: stateEntry, data: state });\n\t\treturn state;\n\t};\n\n\tconst tool = api.registerTool({\n\t\tname: \"todo_update\",\n\t\tlabel: \"Update todo\",\n\t\tdescription: \"Update a todo that softly references a plan by id\",\n\t\tparameters: todoSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = objectInput(first, second);\n\t\t\tconst id = text(input.id, \"id\");\n\t\t\tconst planId = text(input.planId, \"planId\");\n\t\t\tconst title = text(input.title, \"title\");\n\t\t\tconst nextRevision = revision(input.revision);\n\t\t\tconst current = state.todos.find((item) => item.id === id);\n\t\t\tif (current && nextRevision <= current.revision) throw new Error(`stale todo revision: ${id}`);\n\t\t\tif (current?.status === \"done\") throw new Error(`todo is terminal and cannot be updated: ${id}`);\n\t\t\tconst status =\n\t\t\t\tinput.status === undefined\n\t\t\t\t\t? (current?.status ?? \"pending\")\n\t\t\t\t\t: (text(input.status, \"status\") as TodoRecord[\"status\"]);\n\t\t\tif (![\"pending\", \"done\", \"blocked\"].includes(status)) throw new Error(\"invalid todo status\");\n\t\t\tconst values = evidence(input.evidence);\n\t\t\tif (status === \"done\" && values.length === 0) {\n\t\t\t\tthrow new Error(\"todo completion requires at least one evidence reference\");\n\t\t\t}\n\t\t\tconst record: TodoRecord = { id, planId, title, status, revision: nextRevision, evidence: values };\n\t\t\tstate = { ...state, todos: [...state.todos.filter((item) => item.id !== id), record] };\n\t\t\tconst saved = await persist();\n\t\t\tif (planKnownToSession(api, planId)) return saved;\n\t\t\treturn {\n\t\t\t\t...saved,\n\t\t\t\twarning: `planId \"${planId}\" does not match any plan in this session's plan state; the todo keeps the soft reference, but the plan is unknown`,\n\t\t\t};\n\t\t},\n\t});\n\n\treturn [tool];\n}\n"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Todo-set capability plugin: a self-contained todo state machine, extracted\n * from the host's `src/capabilities/todo-set.ts` (D-075 S4, first B-class\n * batch).\n *\n * Todos persist as append-only `todo.state` session entries and every change\n * is announced as a `todo.changed` event. `planId` is a soft reference: a\n * plain string whose target is checked ADVISORILY against the plan plugin's\n * LATEST `plan.state` snapshot (the same latest-snapshot semantics as this\n * plugin's own {@link loadState}); an unknown id yields a response warning,\n * never a failure (the same #10 pattern as the plan plugin's goalId check).\n * The plugin only consumes the public `session`, `events`, and `tools`\n * capabilities and never writes state owned by another plugin.\n */\n\nimport { Type } from \"typebox\";\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\n\nexport interface TodoRecord {\n\tid: string;\n\t/** Soft reference: a plain string; unknown ids yield a response warning, not a failure. */\n\tplanId: string;\n\ttitle: string;\n\tstatus: \"pending\" | \"done\" | \"blocked\";\n\trevision: number;\n\tevidence: string[];\n}\n\nexport interface TodoState {\n\ttodos: TodoRecord[];\n}\n\nconst stateEntry = \"todo.state\";\n/** Session protocol key of the plan plugin's snapshot entry (plugins/plan). */\nconst planStateEntry = \"plan.state\";\n\nconst todoSchema = Type.Object({\n\tid: Type.String({ description: \"Stable id of the todo to create or update\" }),\n\tplanId: Type.String({\n\t\tdescription:\n\t\t\t\"Soft reference to a plan id; an id unknown to this session's plan state yields a response warning, not a failure\",\n\t}),\n\ttitle: Type.String({ description: \"Human-readable todo title\" }),\n\trevision: Type.Integer({\n\t\tdescription: \"New revision counter; must be greater than the stored revision of an existing todo\",\n\t\tminimum: 0,\n\t}),\n\tstatus: Type.Optional(\n\t\tType.Union([Type.Literal(\"pending\"), Type.Literal(\"done\"), Type.Literal(\"blocked\")], {\n\t\t\tdescription: \"Next status; defaults to the stored status for existing todos, otherwise pending\",\n\t\t}),\n\t),\n\tevidence: Type.Optional(\n\t\tType.Array(Type.String(), {\n\t\t\tdescription: \"Evidence references; at least one is required when status is done\",\n\t\t}),\n\t),\n});\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"Todo input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction text(value: unknown, name: string): string {\n\tif (typeof value !== \"string\" || value.trim() === \"\") throw new Error(`${name} must be a non-empty string`);\n\treturn value;\n}\n\nfunction revision(value: unknown): number {\n\tif (!Number.isInteger(value) || (value as number) < 0) throw new Error(\"revision must be a non-negative integer\");\n\treturn value as number;\n}\n\nfunction evidence(value: unknown): string[] {\n\tif (value === undefined) return [];\n\tif (!Array.isArray(value) || value.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new Error(\"evidence must be a string array\");\n\t}\n\treturn [...(value as string[])];\n}\n\nfunction loadState(api: CapabilityAPI): TodoState {\n\tconst entries = api.session?.getBranchEntries() ?? [];\n\tconst entry = [...entries].reverse().find((item) => item.customType === stateEntry);\n\tif (!entry?.data || typeof entry.data !== \"object\") return { todos: [] };\n\tconst data = entry.data as Partial<TodoState>;\n\treturn { todos: Array.isArray(data.todos) ? [...data.todos] : [] };\n}\n\n/**\n * Whether the session branch's LATEST `plan.state` snapshot knows a plan with\n * the given id (the same latest-snapshot semantics as {@link loadState} and\n * the same advisory pattern as the plan plugin's goalKnownToSession). The\n * check is advisory only (#10): a missing plan downgrades to a warning in the\n * tool response — the soft-reference write path never fails on it.\n */\nfunction planKnownToSession(api: CapabilityAPI, planId: string): boolean {\n\tconst entries = api.session?.getBranchEntries() ?? [];\n\tfor (let index = entries.length - 1; index >= 0; index -= 1) {\n\t\tconst entry = entries[index];\n\t\tif (entry.customType !== planStateEntry) continue;\n\t\tconst data = entry.data;\n\t\tif (!data || typeof data !== \"object\" || Array.isArray(data)) return false;\n\t\tconst plans = (data as { plans?: unknown }).plans;\n\t\tif (!Array.isArray(plans)) return false;\n\t\treturn plans.some(\n\t\t\t(plan) => plan !== null && typeof plan === \"object\" && (plan as { id?: unknown }).id === planId,\n\t\t);\n\t}\n\treturn false;\n}\n\nexport function createCapability(\n\tapi: CapabilityAPI,\n): Promise<readonly DisposableRegistration[]> | readonly DisposableRegistration[] {\n\tlet state = loadState(api);\n\n\tconst persist = async (): Promise<TodoState> => {\n\t\tapi.session?.appendEntry(stateEntry, state);\n\t\tawait api.publish({ type: \"todo.changed\", version: 1, correlationId: stateEntry, data: state });\n\t\treturn state;\n\t};\n\n\tconst tool = api.registerTool({\n\t\tname: \"todo_update\",\n\t\tlabel: \"Update todo\",\n\t\tdescription: \"Update a todo that softly references a plan by id\",\n\t\tparameters: todoSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = objectInput(first, second);\n\t\t\tconst id = text(input.id, \"id\");\n\t\t\tconst planId = text(input.planId, \"planId\");\n\t\t\tconst title = text(input.title, \"title\");\n\t\t\tconst nextRevision = revision(input.revision);\n\t\t\tconst current = state.todos.find((item) => item.id === id);\n\t\t\tif (current && nextRevision <= current.revision) throw new Error(`stale todo revision: ${id}`);\n\t\t\tif (current?.status === \"done\") throw new Error(`todo is terminal and cannot be updated: ${id}`);\n\t\t\tconst status =\n\t\t\t\tinput.status === undefined\n\t\t\t\t\t? (current?.status ?? \"pending\")\n\t\t\t\t\t: (text(input.status, \"status\") as TodoRecord[\"status\"]);\n\t\t\tif (![\"pending\", \"done\", \"blocked\"].includes(status)) throw new Error(\"invalid todo status\");\n\t\t\tconst values = evidence(input.evidence);\n\t\t\tif (status === \"done\" && values.length === 0) {\n\t\t\t\tthrow new Error(\"todo completion requires at least one evidence reference\");\n\t\t\t}\n\t\t\tconst record: TodoRecord = { id, planId, title, status, revision: nextRevision, evidence: values };\n\t\t\tstate = { ...state, todos: [...state.todos.filter((item) => item.id !== id), record] };\n\t\t\tconst saved = await persist();\n\t\t\tif (planKnownToSession(api, planId)) return saved;\n\t\t\treturn {\n\t\t\t\t...saved,\n\t\t\t\twarning: `planId \"${planId}\" does not match any plan in this session's plan state; the todo keeps the soft reference, but the plan is unknown`,\n\t\t\t};\n\t\t},\n\t});\n\n\treturn [tool];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Todo-set capability plugin: a self-contained todo state machine, extracted\n * from the host's `src/capabilities/todo-set.ts` (D-075 S4, first B-class\n * batch).\n *\n * Todos persist as append-only `todo.state` session entries and every change\n * is announced as a `todo.changed` event. `planId` is a soft reference: a\n * plain string whose target is checked ADVISORILY against the plan plugin's\n * LATEST `plan.state` snapshot (the same latest-snapshot semantics as this\n * plugin's own {@link loadState}); an unknown id yields a response warning,\n * never a failure (the same #10 pattern as the plan plugin's goalId check).\n * The plugin only consumes the public `session`, `events`, and `tools`\n * capabilities and never writes state owned by another plugin.\n */\n\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\n\nexport interface TodoRecord {\n\tid: string;\n\t/** Soft reference: a plain string; unknown ids yield a response warning, not a failure. */\n\tplanId: string;\n\ttitle: string;\n\tstatus: \"pending\" | \"done\" | \"blocked\";\n\trevision: number;\n\tevidence: string[];\n}\n\nexport interface TodoState {\n\ttodos: TodoRecord[];\n}\n\nconst stateEntry = \"todo.state\";\n/** Session protocol key of the plan plugin's snapshot entry (plugins/plan). */\nconst planStateEntry = \"plan.state\";\n\nconst todoSchema = Type.Object({\n\tid: Type.String({ description: \"Stable id of the todo to create or update\" }),\n\tplanId: Type.String({\n\t\tdescription:\n\t\t\t\"Soft reference to a plan id; an id unknown to this session's plan state yields a response warning, not a failure\",\n\t}),\n\ttitle: Type.String({ description: \"Human-readable todo title\" }),\n\trevision: Type.Integer({\n\t\tdescription: \"New revision counter; must be greater than the stored revision of an existing todo\",\n\t\tminimum: 0,\n\t}),\n\tstatus: Type.Optional(\n\t\tType.Union([Type.Literal(\"pending\"), Type.Literal(\"done\"), Type.Literal(\"blocked\")], {\n\t\t\tdescription: \"Next status; defaults to the stored status for existing todos, otherwise pending\",\n\t\t}),\n\t),\n\tevidence: Type.Optional(\n\t\tType.Array(Type.String(), {\n\t\t\tdescription: \"Evidence references; at least one is required when status is done\",\n\t\t}),\n\t),\n});\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"Todo input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction text(value: unknown, name: string): string {\n\tif (typeof value !== \"string\" || value.trim() === \"\") throw new Error(`${name} must be a non-empty string`);\n\treturn value;\n}\n\nfunction revision(value: unknown): number {\n\tif (!Number.isInteger(value) || (value as number) < 0) throw new Error(\"revision must be a non-negative integer\");\n\treturn value as number;\n}\n\nfunction evidence(value: unknown): string[] {\n\tif (value === undefined) return [];\n\tif (!Array.isArray(value) || value.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new Error(\"evidence must be a string array\");\n\t}\n\treturn [...(value as string[])];\n}\n\nfunction loadState(api: CapabilityAPI): TodoState {\n\tconst entries = api.session?.getBranchEntries() ?? [];\n\tconst entry = [...entries].reverse().find((item) => item.customType === stateEntry);\n\tif (!entry?.data || typeof entry.data !== \"object\") return { todos: [] };\n\tconst data = entry.data as Partial<TodoState>;\n\treturn { todos: Array.isArray(data.todos) ? [...data.todos] : [] };\n}\n\n/**\n * Whether the session branch's LATEST `plan.state` snapshot knows a plan with\n * the given id (the same latest-snapshot semantics as {@link loadState} and\n * the same advisory pattern as the plan plugin's goalKnownToSession). The\n * check is advisory only (#10): a missing plan downgrades to a warning in the\n * tool response — the soft-reference write path never fails on it.\n */\nfunction planKnownToSession(api: CapabilityAPI, planId: string): boolean {\n\tconst entries = api.session?.getBranchEntries() ?? [];\n\tfor (let index = entries.length - 1; index >= 0; index -= 1) {\n\t\tconst entry = entries[index];\n\t\tif (entry.customType !== planStateEntry) continue;\n\t\tconst data = entry.data;\n\t\tif (!data || typeof data !== \"object\" || Array.isArray(data)) return false;\n\t\tconst plans = (data as { plans?: unknown }).plans;\n\t\tif (!Array.isArray(plans)) return false;\n\t\treturn plans.some(\n\t\t\t(plan) => plan !== null && typeof plan === \"object\" && (plan as { id?: unknown }).id === planId,\n\t\t);\n\t}\n\treturn false;\n}\n\nexport function createCapability(\n\tapi: CapabilityAPI,\n): Promise<readonly DisposableRegistration[]> | readonly DisposableRegistration[] {\n\tlet state = loadState(api);\n\n\tconst persist = async (): Promise<TodoState> => {\n\t\tapi.session?.appendEntry(stateEntry, state);\n\t\tawait api.publish({ type: \"todo.changed\", version: 1, correlationId: stateEntry, data: state });\n\t\treturn state;\n\t};\n\n\tconst tool = api.registerTool({\n\t\tname: \"todo_update\",\n\t\tlabel: \"Update todo\",\n\t\tdescription: \"Update a todo that softly references a plan by id\",\n\t\tparameters: todoSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = objectInput(first, second);\n\t\t\tconst id = text(input.id, \"id\");\n\t\t\tconst planId = text(input.planId, \"planId\");\n\t\t\tconst title = text(input.title, \"title\");\n\t\t\tconst nextRevision = revision(input.revision);\n\t\t\tconst current = state.todos.find((item) => item.id === id);\n\t\t\tif (current && nextRevision <= current.revision) throw new Error(`stale todo revision: ${id}`);\n\t\t\tif (current?.status === \"done\") throw new Error(`todo is terminal and cannot be updated: ${id}`);\n\t\t\tconst status =\n\t\t\t\tinput.status === undefined\n\t\t\t\t\t? (current?.status ?? \"pending\")\n\t\t\t\t\t: (text(input.status, \"status\") as TodoRecord[\"status\"]);\n\t\t\tif (![\"pending\", \"done\", \"blocked\"].includes(status)) throw new Error(\"invalid todo status\");\n\t\t\tconst values = evidence(input.evidence);\n\t\t\tif (status === \"done\" && values.length === 0) {\n\t\t\t\tthrow new Error(\"todo completion requires at least one evidence reference\");\n\t\t\t}\n\t\t\tconst record: TodoRecord = { id, planId, title, status, revision: nextRevision, evidence: values };\n\t\t\tstate = { ...state, todos: [...state.todos.filter((item) => item.id !== id), record] };\n\t\t\tconst saved = await persist();\n\t\t\tif (planKnownToSession(api, planId)) return saved;\n\t\t\treturn {\n\t\t\t\t...saved,\n\t\t\t\twarning: `planId \"${planId}\" does not match any plan in this session's plan state; the todo keeps the soft reference, but the plan is unknown`,\n\t\t\t};\n\t\t},\n\t});\n\n\treturn [tool];\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-agent-forge/plugin-todo-set",
|
|
3
|
-
"version": "0.85.
|
|
3
|
+
"version": "0.85.2",
|
|
4
4
|
"description": "Todo-set capability plugin for Agent Forge (todo_update state machine over session entries)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"node": ">=22.19.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@agent-forge/plugin-sdk": "^0.85.
|
|
46
|
+
"@agent-forge/plugin-sdk": "^0.85.2",
|
|
47
47
|
"typebox": "1.3.27"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@agent-forge/agent-forge": "^0.85.
|
|
50
|
+
"@agent-forge/agent-forge": "^0.85.2",
|
|
51
51
|
"shx": "0.4.0",
|
|
52
52
|
"vitest": "4.1.9"
|
|
53
53
|
}
|
package/plugin.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "agent-forge.plugin.todo-set",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.2",
|
|
4
4
|
"apiVersion": "1-draft",
|
|
5
5
|
"entry": "./dist/entry.js",
|
|
6
6
|
"hostVersion": ">=0.84.4",
|
|
7
|
-
"requiredCapabilities": [
|
|
7
|
+
"requiredCapabilities": [
|
|
8
|
+
"session",
|
|
9
|
+
"events",
|
|
10
|
+
"tools"
|
|
11
|
+
]
|
|
8
12
|
}
|