@ai-agent-forge/plugin-plan 0.85.1 → 0.85.3

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 CHANGED
@@ -1,11 +1,19 @@
1
1
  {
2
2
  "packageFormatVersion": 1,
3
3
  "id": "agent-forge.plugin.plan",
4
- "pluginVersion": "0.84.4",
4
+ "pluginVersion": "0.85.3",
5
5
  "apiVersion": "1",
6
6
  "minHostVersion": "0.84.4",
7
7
  "platform": {
8
- "os": ["windows", "macos", "linux"],
9
- "runtime": [{ "name": "node" }]
8
+ "os": [
9
+ "windows",
10
+ "macos",
11
+ "linux"
12
+ ],
13
+ "runtime": [
14
+ {
15
+ "name": "node"
16
+ }
17
+ ]
10
18
  }
11
19
  }
@@ -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,qBAAqB,CAClD,GAAG,EAAE,aAAa,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC,CAWlC","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 createPlanPluginEntry(\n\tapi: CapabilityAPI,\n): 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"]}
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,qBAAqB,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC,CAWxG","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 createPlanPluginEntry(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,qBAAqB,CAClD,GAAkB,EACiB;IACnC,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 createPlanPluginEntry(\n\tapi: CapabilityAPI,\n): 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"]}
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,qBAAqB,CAAC,GAAkB,EAAoC;IACzG,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 createPlanPluginEntry(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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAErF,MAAM,WAAW,UAAU;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;IACvD,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,CAiDhF","sourcesContent":["/**\n * Plan capability plugin: a self-contained plan state machine, extracted from\n * the host's `src/capabilities/plan.ts` (D-075 S4, first B-class batch).\n *\n * Plans persist as append-only `plan.state` session entries and every change\n * is announced as a `plan.changed` event. `goalId` stays a soft reference: an\n * unknown id never fails the write (#10) — but the execute handler checks the\n * session branch for a matching `goal.state` entry and, when none exists,\n * attaches a `warning` field to the tool response (not to the persisted state)\n * so the model knows the reference points at no goal the session knows about.\n * The plugin only consumes the public `session`, `events`, and `tools`\n * capabilities and reads other plugins' state only through the shared session\n * entry protocol, never through their private memory.\n */\n\nimport { Type } from \"typebox\";\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\n\nexport interface PlanRecord {\n\tid: string;\n\t/** Soft reference: a plain string; unknown ids yield a response warning, not a failure. */\n\tgoalId: string;\n\ttitle: string;\n\tstatus: \"draft\" | \"active\" | \"completed\" | \"cancelled\";\n\trevision: number;\n\tevidence: string[];\n}\n\nexport interface PlanState {\n\tplans: PlanRecord[];\n}\n\nconst stateEntry = \"plan.state\";\n/** Session protocol key of the goal plugin's snapshot entry (plugins/goal). */\nconst goalStateEntry = \"goal.state\";\n\nconst planSchema = Type.Object({\n\tid: Type.String({ description: \"Stable id of the plan to create or update\" }),\n\tgoalId: Type.String({\n\t\tdescription:\n\t\t\t\"Soft reference to a goal id; the write always succeeds, but the response carries a warning when no goal with this id exists in the session's goal state\",\n\t}),\n\ttitle: Type.String({ description: \"Human-readable plan title\" }),\n\trevision: Type.Integer({\n\t\tdescription: \"New revision counter; must be greater than the stored revision of an existing plan\",\n\t\tminimum: 0,\n\t}),\n\tstatus: Type.Optional(\n\t\tType.Union(\n\t\t\t[Type.Literal(\"draft\"), Type.Literal(\"active\"), Type.Literal(\"completed\"), Type.Literal(\"cancelled\")],\n\t\t\t{ description: \"Next status; defaults to the stored status for existing plans, otherwise draft\" },\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 completed\",\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(\"Plan 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): PlanState {\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 { plans: [] };\n\tconst data = entry.data as Partial<PlanState>;\n\treturn { plans: Array.isArray(data.plans) ? [...data.plans] : [] };\n}\n\n/**\n * Whether the session branch's LATEST `goal.state` snapshot knows a goal with\n * the given id (the same latest-snapshot semantics as {@link loadState}). The\n * check is advisory only (#10): a missing goal downgrades to a warning in the\n * tool response — the soft-reference write path never fails on it.\n */\nfunction goalKnownToSession(api: CapabilityAPI, goalId: 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 !== goalStateEntry) continue;\n\t\tconst data = entry.data;\n\t\tif (!data || typeof data !== \"object\" || Array.isArray(data)) return false;\n\t\tconst goals = (data as { goals?: unknown }).goals;\n\t\tif (!Array.isArray(goals)) return false;\n\t\treturn goals.some(\n\t\t\t(goal) => goal !== null && typeof goal === \"object\" && (goal as { id?: unknown }).id === goalId,\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<PlanState> => {\n\t\tapi.session?.appendEntry(stateEntry, state);\n\t\tawait api.publish({ type: \"plan.changed\", version: 1, correlationId: stateEntry, data: state });\n\t\treturn state;\n\t};\n\n\tconst tool = api.registerTool({\n\t\tname: \"plan_set\",\n\t\tlabel: \"Set plan\",\n\t\tdescription: \"Create or update a plan that softly references a goal by id\",\n\t\tparameters: planSchema,\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 goalId = text(input.goalId, \"goalId\");\n\t\t\tconst title = text(input.title, \"title\");\n\t\t\tconst nextRevision = revision(input.revision);\n\t\t\tconst current = state.plans.find((item) => item.id === id);\n\t\t\tif (current && nextRevision <= current.revision) throw new Error(`stale plan revision: ${id}`);\n\t\t\tif (current?.status === \"completed\" || current?.status === \"cancelled\") {\n\t\t\t\tthrow new Error(`plan is terminal and cannot be updated: ${id}`);\n\t\t\t}\n\t\t\tconst status =\n\t\t\t\tinput.status === undefined\n\t\t\t\t\t? (current?.status ?? \"draft\")\n\t\t\t\t\t: (text(input.status, \"status\") as PlanRecord[\"status\"]);\n\t\t\tif (![\"draft\", \"active\", \"completed\", \"cancelled\"].includes(status)) throw new Error(\"invalid plan status\");\n\t\t\tconst values = evidence(input.evidence);\n\t\t\tif (status === \"completed\" && values.length === 0) {\n\t\t\t\tthrow new Error(\"plan completion requires at least one evidence reference\");\n\t\t\t}\n\t\t\tconst record: PlanRecord = { id, goalId, title, status, revision: nextRevision, evidence: values };\n\t\t\tstate = { ...state, plans: [...state.plans.filter((item) => item.id !== id), record] };\n\t\t\tconst persisted = await persist();\n\t\t\t// Soft reference stays writable (#10): an unknown goalId only attaches\n\t\t\t// a warning to the RESPONSE — the persisted state and the\n\t\t\t// plan.changed event keep the plain PlanState shape.\n\t\t\tif (goalKnownToSession(api, goalId)) return persisted;\n\t\t\treturn {\n\t\t\t\t...persisted,\n\t\t\t\twarning: `goalId \"${goalId}\" does not match any goal in this session's goal state; the plan keeps the soft reference, but the goal is unknown`,\n\t\t\t};\n\t\t},\n\t});\n\n\treturn [tool];\n}\n"]}
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,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;IACvD,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,CAiDhF","sourcesContent":["/**\n * Plan capability plugin: a self-contained plan state machine, extracted from\n * the host's `src/capabilities/plan.ts` (D-075 S4, first B-class batch).\n *\n * Plans persist as append-only `plan.state` session entries and every change\n * is announced as a `plan.changed` event. `goalId` stays a soft reference: an\n * unknown id never fails the write (#10) — but the execute handler checks the\n * session branch for a matching `goal.state` entry and, when none exists,\n * attaches a `warning` field to the tool response (not to the persisted state)\n * so the model knows the reference points at no goal the session knows about.\n * The plugin only consumes the public `session`, `events`, and `tools`\n * capabilities and reads other plugins' state only through the shared session\n * entry protocol, never through their private memory.\n */\n\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\n\nexport interface PlanRecord {\n\tid: string;\n\t/** Soft reference: a plain string; unknown ids yield a response warning, not a failure. */\n\tgoalId: string;\n\ttitle: string;\n\tstatus: \"draft\" | \"active\" | \"completed\" | \"cancelled\";\n\trevision: number;\n\tevidence: string[];\n}\n\nexport interface PlanState {\n\tplans: PlanRecord[];\n}\n\nconst stateEntry = \"plan.state\";\n/** Session protocol key of the goal plugin's snapshot entry (plugins/goal). */\nconst goalStateEntry = \"goal.state\";\n\nconst planSchema = Type.Object({\n\tid: Type.String({ description: \"Stable id of the plan to create or update\" }),\n\tgoalId: Type.String({\n\t\tdescription:\n\t\t\t\"Soft reference to a goal id; the write always succeeds, but the response carries a warning when no goal with this id exists in the session's goal state\",\n\t}),\n\ttitle: Type.String({ description: \"Human-readable plan title\" }),\n\trevision: Type.Integer({\n\t\tdescription: \"New revision counter; must be greater than the stored revision of an existing plan\",\n\t\tminimum: 0,\n\t}),\n\tstatus: Type.Optional(\n\t\tType.Union(\n\t\t\t[Type.Literal(\"draft\"), Type.Literal(\"active\"), Type.Literal(\"completed\"), Type.Literal(\"cancelled\")],\n\t\t\t{ description: \"Next status; defaults to the stored status for existing plans, otherwise draft\" },\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 completed\",\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(\"Plan 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): PlanState {\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 { plans: [] };\n\tconst data = entry.data as Partial<PlanState>;\n\treturn { plans: Array.isArray(data.plans) ? [...data.plans] : [] };\n}\n\n/**\n * Whether the session branch's LATEST `goal.state` snapshot knows a goal with\n * the given id (the same latest-snapshot semantics as {@link loadState}). The\n * check is advisory only (#10): a missing goal downgrades to a warning in the\n * tool response — the soft-reference write path never fails on it.\n */\nfunction goalKnownToSession(api: CapabilityAPI, goalId: 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 !== goalStateEntry) continue;\n\t\tconst data = entry.data;\n\t\tif (!data || typeof data !== \"object\" || Array.isArray(data)) return false;\n\t\tconst goals = (data as { goals?: unknown }).goals;\n\t\tif (!Array.isArray(goals)) return false;\n\t\treturn goals.some(\n\t\t\t(goal) => goal !== null && typeof goal === \"object\" && (goal as { id?: unknown }).id === goalId,\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<PlanState> => {\n\t\tapi.session?.appendEntry(stateEntry, state);\n\t\tawait api.publish({ type: \"plan.changed\", version: 1, correlationId: stateEntry, data: state });\n\t\treturn state;\n\t};\n\n\tconst tool = api.registerTool({\n\t\tname: \"plan_set\",\n\t\tlabel: \"Set plan\",\n\t\tdescription: \"Create or update a plan that softly references a goal by id\",\n\t\tparameters: planSchema,\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 goalId = text(input.goalId, \"goalId\");\n\t\t\tconst title = text(input.title, \"title\");\n\t\t\tconst nextRevision = revision(input.revision);\n\t\t\tconst current = state.plans.find((item) => item.id === id);\n\t\t\tif (current && nextRevision <= current.revision) throw new Error(`stale plan revision: ${id}`);\n\t\t\tif (current?.status === \"completed\" || current?.status === \"cancelled\") {\n\t\t\t\tthrow new Error(`plan is terminal and cannot be updated: ${id}`);\n\t\t\t}\n\t\t\tconst status =\n\t\t\t\tinput.status === undefined\n\t\t\t\t\t? (current?.status ?? \"draft\")\n\t\t\t\t\t: (text(input.status, \"status\") as PlanRecord[\"status\"]);\n\t\t\tif (![\"draft\", \"active\", \"completed\", \"cancelled\"].includes(status)) throw new Error(\"invalid plan status\");\n\t\t\tconst values = evidence(input.evidence);\n\t\t\tif (status === \"completed\" && values.length === 0) {\n\t\t\t\tthrow new Error(\"plan completion requires at least one evidence reference\");\n\t\t\t}\n\t\t\tconst record: PlanRecord = { id, goalId, title, status, revision: nextRevision, evidence: values };\n\t\t\tstate = { ...state, plans: [...state.plans.filter((item) => item.id !== id), record] };\n\t\t\tconst persisted = await persist();\n\t\t\t// Soft reference stays writable (#10): an unknown goalId only attaches\n\t\t\t// a warning to the RESPONSE — the persisted state and the\n\t\t\t// plan.changed event keep the plain PlanState shape.\n\t\t\tif (goalKnownToSession(api, goalId)) return persisted;\n\t\t\treturn {\n\t\t\t\t...persisted,\n\t\t\t\twarning: `goalId \"${goalId}\" does not match any goal in this session's goal state; the plan keeps the soft reference, but the goal 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 * Plan capability plugin: a self-contained plan state machine, extracted from\n * the host's `src/capabilities/plan.ts` (D-075 S4, first B-class batch).\n *\n * Plans persist as append-only `plan.state` session entries and every change\n * is announced as a `plan.changed` event. `goalId` stays a soft reference: an\n * unknown id never fails the write (#10) — but the execute handler checks the\n * session branch for a matching `goal.state` entry and, when none exists,\n * attaches a `warning` field to the tool response (not to the persisted state)\n * so the model knows the reference points at no goal the session knows about.\n * The plugin only consumes the public `session`, `events`, and `tools`\n * capabilities and reads other plugins' state only through the shared session\n * entry protocol, never through their private memory.\n */\n\nimport { Type } from \"typebox\";\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\n\nexport interface PlanRecord {\n\tid: string;\n\t/** Soft reference: a plain string; unknown ids yield a response warning, not a failure. */\n\tgoalId: string;\n\ttitle: string;\n\tstatus: \"draft\" | \"active\" | \"completed\" | \"cancelled\";\n\trevision: number;\n\tevidence: string[];\n}\n\nexport interface PlanState {\n\tplans: PlanRecord[];\n}\n\nconst stateEntry = \"plan.state\";\n/** Session protocol key of the goal plugin's snapshot entry (plugins/goal). */\nconst goalStateEntry = \"goal.state\";\n\nconst planSchema = Type.Object({\n\tid: Type.String({ description: \"Stable id of the plan to create or update\" }),\n\tgoalId: Type.String({\n\t\tdescription:\n\t\t\t\"Soft reference to a goal id; the write always succeeds, but the response carries a warning when no goal with this id exists in the session's goal state\",\n\t}),\n\ttitle: Type.String({ description: \"Human-readable plan title\" }),\n\trevision: Type.Integer({\n\t\tdescription: \"New revision counter; must be greater than the stored revision of an existing plan\",\n\t\tminimum: 0,\n\t}),\n\tstatus: Type.Optional(\n\t\tType.Union(\n\t\t\t[Type.Literal(\"draft\"), Type.Literal(\"active\"), Type.Literal(\"completed\"), Type.Literal(\"cancelled\")],\n\t\t\t{ description: \"Next status; defaults to the stored status for existing plans, otherwise draft\" },\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 completed\",\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(\"Plan 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): PlanState {\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 { plans: [] };\n\tconst data = entry.data as Partial<PlanState>;\n\treturn { plans: Array.isArray(data.plans) ? [...data.plans] : [] };\n}\n\n/**\n * Whether the session branch's LATEST `goal.state` snapshot knows a goal with\n * the given id (the same latest-snapshot semantics as {@link loadState}). The\n * check is advisory only (#10): a missing goal downgrades to a warning in the\n * tool response — the soft-reference write path never fails on it.\n */\nfunction goalKnownToSession(api: CapabilityAPI, goalId: 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 !== goalStateEntry) continue;\n\t\tconst data = entry.data;\n\t\tif (!data || typeof data !== \"object\" || Array.isArray(data)) return false;\n\t\tconst goals = (data as { goals?: unknown }).goals;\n\t\tif (!Array.isArray(goals)) return false;\n\t\treturn goals.some(\n\t\t\t(goal) => goal !== null && typeof goal === \"object\" && (goal as { id?: unknown }).id === goalId,\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<PlanState> => {\n\t\tapi.session?.appendEntry(stateEntry, state);\n\t\tawait api.publish({ type: \"plan.changed\", version: 1, correlationId: stateEntry, data: state });\n\t\treturn state;\n\t};\n\n\tconst tool = api.registerTool({\n\t\tname: \"plan_set\",\n\t\tlabel: \"Set plan\",\n\t\tdescription: \"Create or update a plan that softly references a goal by id\",\n\t\tparameters: planSchema,\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 goalId = text(input.goalId, \"goalId\");\n\t\t\tconst title = text(input.title, \"title\");\n\t\t\tconst nextRevision = revision(input.revision);\n\t\t\tconst current = state.plans.find((item) => item.id === id);\n\t\t\tif (current && nextRevision <= current.revision) throw new Error(`stale plan revision: ${id}`);\n\t\t\tif (current?.status === \"completed\" || current?.status === \"cancelled\") {\n\t\t\t\tthrow new Error(`plan is terminal and cannot be updated: ${id}`);\n\t\t\t}\n\t\t\tconst status =\n\t\t\t\tinput.status === undefined\n\t\t\t\t\t? (current?.status ?? \"draft\")\n\t\t\t\t\t: (text(input.status, \"status\") as PlanRecord[\"status\"]);\n\t\t\tif (![\"draft\", \"active\", \"completed\", \"cancelled\"].includes(status)) throw new Error(\"invalid plan status\");\n\t\t\tconst values = evidence(input.evidence);\n\t\t\tif (status === \"completed\" && values.length === 0) {\n\t\t\t\tthrow new Error(\"plan completion requires at least one evidence reference\");\n\t\t\t}\n\t\t\tconst record: PlanRecord = { id, goalId, title, status, revision: nextRevision, evidence: values };\n\t\t\tstate = { ...state, plans: [...state.plans.filter((item) => item.id !== id), record] };\n\t\t\tconst persisted = await persist();\n\t\t\t// Soft reference stays writable (#10): an unknown goalId only attaches\n\t\t\t// a warning to the RESPONSE — the persisted state and the\n\t\t\t// plan.changed event keep the plain PlanState shape.\n\t\t\tif (goalKnownToSession(api, goalId)) return persisted;\n\t\t\treturn {\n\t\t\t\t...persisted,\n\t\t\t\twarning: `goalId \"${goalId}\" does not match any goal in this session's goal state; the plan keeps the soft reference, but the goal 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 * Plan capability plugin: a self-contained plan state machine, extracted from\n * the host's `src/capabilities/plan.ts` (D-075 S4, first B-class batch).\n *\n * Plans persist as append-only `plan.state` session entries and every change\n * is announced as a `plan.changed` event. `goalId` stays a soft reference: an\n * unknown id never fails the write (#10) — but the execute handler checks the\n * session branch for a matching `goal.state` entry and, when none exists,\n * attaches a `warning` field to the tool response (not to the persisted state)\n * so the model knows the reference points at no goal the session knows about.\n * The plugin only consumes the public `session`, `events`, and `tools`\n * capabilities and reads other plugins' state only through the shared session\n * entry protocol, never through their private memory.\n */\n\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\n\nexport interface PlanRecord {\n\tid: string;\n\t/** Soft reference: a plain string; unknown ids yield a response warning, not a failure. */\n\tgoalId: string;\n\ttitle: string;\n\tstatus: \"draft\" | \"active\" | \"completed\" | \"cancelled\";\n\trevision: number;\n\tevidence: string[];\n}\n\nexport interface PlanState {\n\tplans: PlanRecord[];\n}\n\nconst stateEntry = \"plan.state\";\n/** Session protocol key of the goal plugin's snapshot entry (plugins/goal). */\nconst goalStateEntry = \"goal.state\";\n\nconst planSchema = Type.Object({\n\tid: Type.String({ description: \"Stable id of the plan to create or update\" }),\n\tgoalId: Type.String({\n\t\tdescription:\n\t\t\t\"Soft reference to a goal id; the write always succeeds, but the response carries a warning when no goal with this id exists in the session's goal state\",\n\t}),\n\ttitle: Type.String({ description: \"Human-readable plan title\" }),\n\trevision: Type.Integer({\n\t\tdescription: \"New revision counter; must be greater than the stored revision of an existing plan\",\n\t\tminimum: 0,\n\t}),\n\tstatus: Type.Optional(\n\t\tType.Union(\n\t\t\t[Type.Literal(\"draft\"), Type.Literal(\"active\"), Type.Literal(\"completed\"), Type.Literal(\"cancelled\")],\n\t\t\t{ description: \"Next status; defaults to the stored status for existing plans, otherwise draft\" },\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 completed\",\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(\"Plan 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): PlanState {\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 { plans: [] };\n\tconst data = entry.data as Partial<PlanState>;\n\treturn { plans: Array.isArray(data.plans) ? [...data.plans] : [] };\n}\n\n/**\n * Whether the session branch's LATEST `goal.state` snapshot knows a goal with\n * the given id (the same latest-snapshot semantics as {@link loadState}). The\n * check is advisory only (#10): a missing goal downgrades to a warning in the\n * tool response — the soft-reference write path never fails on it.\n */\nfunction goalKnownToSession(api: CapabilityAPI, goalId: 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 !== goalStateEntry) continue;\n\t\tconst data = entry.data;\n\t\tif (!data || typeof data !== \"object\" || Array.isArray(data)) return false;\n\t\tconst goals = (data as { goals?: unknown }).goals;\n\t\tif (!Array.isArray(goals)) return false;\n\t\treturn goals.some(\n\t\t\t(goal) => goal !== null && typeof goal === \"object\" && (goal as { id?: unknown }).id === goalId,\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<PlanState> => {\n\t\tapi.session?.appendEntry(stateEntry, state);\n\t\tawait api.publish({ type: \"plan.changed\", version: 1, correlationId: stateEntry, data: state });\n\t\treturn state;\n\t};\n\n\tconst tool = api.registerTool({\n\t\tname: \"plan_set\",\n\t\tlabel: \"Set plan\",\n\t\tdescription: \"Create or update a plan that softly references a goal by id\",\n\t\tparameters: planSchema,\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 goalId = text(input.goalId, \"goalId\");\n\t\t\tconst title = text(input.title, \"title\");\n\t\t\tconst nextRevision = revision(input.revision);\n\t\t\tconst current = state.plans.find((item) => item.id === id);\n\t\t\tif (current && nextRevision <= current.revision) throw new Error(`stale plan revision: ${id}`);\n\t\t\tif (current?.status === \"completed\" || current?.status === \"cancelled\") {\n\t\t\t\tthrow new Error(`plan is terminal and cannot be updated: ${id}`);\n\t\t\t}\n\t\t\tconst status =\n\t\t\t\tinput.status === undefined\n\t\t\t\t\t? (current?.status ?? \"draft\")\n\t\t\t\t\t: (text(input.status, \"status\") as PlanRecord[\"status\"]);\n\t\t\tif (![\"draft\", \"active\", \"completed\", \"cancelled\"].includes(status)) throw new Error(\"invalid plan status\");\n\t\t\tconst values = evidence(input.evidence);\n\t\t\tif (status === \"completed\" && values.length === 0) {\n\t\t\t\tthrow new Error(\"plan completion requires at least one evidence reference\");\n\t\t\t}\n\t\t\tconst record: PlanRecord = { id, goalId, title, status, revision: nextRevision, evidence: values };\n\t\t\tstate = { ...state, plans: [...state.plans.filter((item) => item.id !== id), record] };\n\t\t\tconst persisted = await persist();\n\t\t\t// Soft reference stays writable (#10): an unknown goalId only attaches\n\t\t\t// a warning to the RESPONSE — the persisted state and the\n\t\t\t// plan.changed event keep the plain PlanState shape.\n\t\t\tif (goalKnownToSession(api, goalId)) return persisted;\n\t\t\treturn {\n\t\t\t\t...persisted,\n\t\t\t\twarning: `goalId \"${goalId}\" does not match any goal in this session's goal state; the plan keeps the soft reference, but the goal 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-plan",
3
- "version": "0.85.1",
3
+ "version": "0.85.3",
4
4
  "description": "Plan capability plugin for Agent Forge (plan_set 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.1",
46
+ "@agent-forge/plugin-sdk": "^0.85.3",
47
47
  "typebox": "1.3.27"
48
48
  },
49
49
  "devDependencies": {
50
- "@agent-forge/agent-forge": "^0.85.1",
50
+ "@agent-forge/agent-forge": "^0.85.3",
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.plan",
3
- "version": "0.84.4",
3
+ "version": "0.85.3",
4
4
  "apiVersion": "1-draft",
5
5
  "entry": "./dist/entry.js",
6
6
  "hostVersion": ">=0.84.4",
7
- "requiredCapabilities": ["session", "events", "tools"]
7
+ "requiredCapabilities": [
8
+ "session",
9
+ "events",
10
+ "tools"
11
+ ]
8
12
  }