@ai-agent-forge/plugin-goal 0.85.0

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/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @agent-forge/plugin-goal
2
+
3
+ Goal capability plugin for [Agent Forge](https://github.com/agent-forge/agent-forge), extracted from the host's
4
+ `src/capabilities/goal.ts` (D-075 S4, first B-class batch). It registers a single tool, `goal_set`: a self-contained
5
+ goal state machine with explicit revisions and evidence-gated completion.
6
+
7
+ Goals persist as append-only `goal.state` session entries and every change is announced as a `goal.changed` event. The
8
+ plugin only consumes the public `session`, `events`, and `tools` capabilities, keeps soft string references to other
9
+ domains (plans reference goals by plain id), and never validates state owned by another plugin.
10
+
11
+ `goal_set` carries the D-071 task tools guide on its `promptGuidelines`, so the model-facing guidance renders whenever
12
+ the tool face is active.
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ # from a local checkout
18
+ agent-forge plugin install ./plugins/goal
19
+
20
+ # from npm
21
+ agent-forge plugin install @ai-agent-forge/plugin-goal
22
+ ```
23
+
24
+ Suites declare the capability through the `builtin.goal` short id; after the S4 split that id is assembled from this
25
+ package through the install store (bootstrap factory manifest or manual install).
@@ -0,0 +1,11 @@
1
+ {
2
+ "packageFormatVersion": 1,
3
+ "id": "agent-forge.plugin.goal",
4
+ "pluginVersion": "0.84.4",
5
+ "apiVersion": "1",
6
+ "minHostVersion": "0.84.4",
7
+ "platform": {
8
+ "os": ["windows", "macos", "linux"],
9
+ "runtime": [{ "name": "node" }]
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ import type { CapabilityAPI } from "@agent-forge/plugin-sdk";
2
+ /**
3
+ * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
4
+ *
5
+ * The capability factory returns its registrations as an array, while the
6
+ * manifest loader contract expects `void` or a single `{ dispose() }` instance
7
+ * (`normalizePluginInstance` does not accept arrays). The wrapper collects the
8
+ * registrations and disposes them in reverse order on plugin teardown — the
9
+ * same disposal semantics as the host's embedded `syncRegistrationFactory`.
10
+ */
11
+ export default function createGoalPluginEntry(api: CapabilityAPI): Promise<{
12
+ dispose: () => void;
13
+ }>;
14
+ //# sourceMappingURL=entry.d.ts.map
@@ -0,0 +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 createGoalPluginEntry(\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"]}
package/dist/entry.js ADDED
@@ -0,0 +1,23 @@
1
+ import { createCapability } from "./index.js";
2
+ /**
3
+ * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
4
+ *
5
+ * The capability factory returns its registrations as an array, while the
6
+ * manifest loader contract expects `void` or a single `{ dispose() }` instance
7
+ * (`normalizePluginInstance` does not accept arrays). The wrapper collects the
8
+ * registrations and disposes them in reverse order on plugin teardown — the
9
+ * same disposal semantics as the host's embedded `syncRegistrationFactory`.
10
+ */
11
+ export default async function createGoalPluginEntry(api) {
12
+ const registrations = await createCapability(api);
13
+ return {
14
+ dispose: () => {
15
+ for (const registration of [...registrations].reverse()) {
16
+ // Registration disposal in the capability runtime is synchronous;
17
+ // void consumes the declared Promise possibility of the contract.
18
+ void registration.dispose();
19
+ }
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +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 createGoalPluginEntry(\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"]}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Goal capability plugin: a self-contained goal state machine, extracted from
3
+ * the host's `src/capabilities/goal.ts` (D-075 S4, first B-class batch).
4
+ *
5
+ * Goals persist as append-only `goal.state` session entries and every change
6
+ * is announced as a `goal.changed` event. The plugin only consumes the public
7
+ * `session`, `events`, and `tools` capabilities, keeps soft string references
8
+ * to other domains, and never validates state owned by another plugin.
9
+ */
10
+ import type { CapabilityAPI, DisposableRegistration } from "@agent-forge/plugin-sdk";
11
+ /**
12
+ * Task tools guide(任务工具引导 #4,原 system-prompt.ts TASK_TOOL_GUIDE,D-071
13
+ * 迁移):tells the model WHEN to use the goal/plan/todo face. It rides
14
+ * goal_set's promptGuidelines so the text lives with the plugin that owns the
15
+ * tools (宪法 §3) and renders whenever the tool face is active.
16
+ */
17
+ export declare const TASK_TOOLS_GUIDE = "Task Management: For multi-step tasks that span multiple turns, use goal_set to define the objective, plan_set to outline the approach, and todo_update to track progress. Skip these tools for simple one-step operations \u2014 just do the work directly.";
18
+ export interface GoalRecord {
19
+ id: string;
20
+ title: string;
21
+ status: "open" | "completed" | "cancelled";
22
+ revision: number;
23
+ evidence: string[];
24
+ }
25
+ export interface GoalState {
26
+ goals: GoalRecord[];
27
+ }
28
+ export declare function createCapability(api: CapabilityAPI): Promise<readonly DisposableRegistration[]> | readonly DisposableRegistration[];
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAErF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,iQAC6N,CAAC;AAE3P,MAAM,WAAW,UAAU;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,WAAW,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,UAAU,EAAE,CAAC;CACpB;AAuDD,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,aAAa,GAChB,OAAO,CAAC,SAAS,sBAAsB,EAAE,CAAC,GAAG,SAAS,sBAAsB,EAAE,CA8ChF","sourcesContent":["/**\n * Goal capability plugin: a self-contained goal state machine, extracted from\n * the host's `src/capabilities/goal.ts` (D-075 S4, first B-class batch).\n *\n * Goals persist as append-only `goal.state` session entries and every change\n * is announced as a `goal.changed` event. The plugin only consumes the public\n * `session`, `events`, and `tools` capabilities, keeps soft string references\n * to other domains, and never validates state owned by another plugin.\n */\n\nimport { Type } from \"typebox\";\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\n\n/**\n * Task tools guide(任务工具引导 #4,原 system-prompt.ts TASK_TOOL_GUIDE,D-071\n * 迁移):tells the model WHEN to use the goal/plan/todo face. It rides\n * goal_set's promptGuidelines so the text lives with the plugin that owns the\n * tools (宪法 §3) and renders whenever the tool face is active.\n */\nexport const TASK_TOOLS_GUIDE =\n\t\"Task Management: For multi-step tasks that span multiple turns, use goal_set to define the objective, plan_set to outline the approach, and todo_update to track progress. Skip these tools for simple one-step operations — just do the work directly.\";\n\nexport interface GoalRecord {\n\tid: string;\n\ttitle: string;\n\tstatus: \"open\" | \"completed\" | \"cancelled\";\n\trevision: number;\n\tevidence: string[];\n}\n\nexport interface GoalState {\n\tgoals: GoalRecord[];\n}\n\nconst stateEntry = \"goal.state\";\n\nconst goalSchema = Type.Object({\n\tid: Type.String({ description: \"Stable id of the goal to create or update\" }),\n\ttitle: Type.String({ description: \"Human-readable goal title\" }),\n\trevision: Type.Integer({\n\t\tdescription: \"New revision counter; must be greater than the stored revision of an existing goal\",\n\t\tminimum: 0,\n\t}),\n\tstatus: Type.Optional(\n\t\tType.Union([Type.Literal(\"open\"), Type.Literal(\"completed\"), Type.Literal(\"cancelled\")], {\n\t\t\tdescription: \"Next status; defaults to the stored status for existing goals, otherwise open\",\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(\"Goal 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): GoalState {\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 { goals: [] };\n\tconst data = entry.data as Partial<GoalState>;\n\treturn { goals: Array.isArray(data.goals) ? [...data.goals] : [] };\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<GoalState> => {\n\t\tapi.session?.appendEntry(stateEntry, state);\n\t\tawait api.publish({ type: \"goal.changed\", version: 1, correlationId: stateEntry, data: state });\n\t\treturn state;\n\t};\n\n\tconst tool = api.registerTool({\n\t\tname: \"goal_set\",\n\t\tlabel: \"Set goal\",\n\t\tpromptGuidelines: [TASK_TOOLS_GUIDE],\n\t\tdescription: \"Create or update a goal with explicit revision and evidence\",\n\t\tparameters: goalSchema,\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 title = text(input.title, \"title\");\n\t\t\tconst nextRevision = revision(input.revision);\n\t\t\tconst current = state.goals.find((item) => item.id === id);\n\t\t\tif (current && nextRevision <= current.revision) throw new Error(`stale goal revision: ${id}`);\n\t\t\tif (current?.status === \"completed\" || current?.status === \"cancelled\") {\n\t\t\t\tthrow new Error(`goal is terminal and cannot be updated: ${id}`);\n\t\t\t}\n\t\t\tconst values = evidence(input.evidence);\n\t\t\tconst record: GoalRecord = {\n\t\t\t\tid,\n\t\t\t\ttitle,\n\t\t\t\tstatus:\n\t\t\t\t\tinput.status === undefined\n\t\t\t\t\t\t? (current?.status ?? \"open\")\n\t\t\t\t\t\t: (text(input.status, \"status\") as GoalRecord[\"status\"]),\n\t\t\t\trevision: nextRevision,\n\t\t\t\tevidence: values,\n\t\t\t};\n\t\t\tif (![\"open\", \"completed\", \"cancelled\"].includes(record.status)) throw new Error(\"invalid goal status\");\n\t\t\tif (record.status === \"completed\" && values.length === 0) {\n\t\t\t\tthrow new Error(\"goal completion requires at least one evidence reference\");\n\t\t\t}\n\t\t\tstate = { ...state, goals: [...state.goals.filter((item) => item.id !== id), record] };\n\t\t\treturn persist();\n\t\t},\n\t});\n\n\treturn [tool];\n}\n"]}
package/dist/index.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Goal capability plugin: a self-contained goal state machine, extracted from
3
+ * the host's `src/capabilities/goal.ts` (D-075 S4, first B-class batch).
4
+ *
5
+ * Goals persist as append-only `goal.state` session entries and every change
6
+ * is announced as a `goal.changed` event. The plugin only consumes the public
7
+ * `session`, `events`, and `tools` capabilities, keeps soft string references
8
+ * to other domains, and never validates state owned by another plugin.
9
+ */
10
+ import { Type } from "typebox";
11
+ /**
12
+ * Task tools guide(任务工具引导 #4,原 system-prompt.ts TASK_TOOL_GUIDE,D-071
13
+ * 迁移):tells the model WHEN to use the goal/plan/todo face. It rides
14
+ * goal_set's promptGuidelines so the text lives with the plugin that owns the
15
+ * tools (宪法 §3) and renders whenever the tool face is active.
16
+ */
17
+ export const TASK_TOOLS_GUIDE = "Task Management: For multi-step tasks that span multiple turns, use goal_set to define the objective, plan_set to outline the approach, and todo_update to track progress. Skip these tools for simple one-step operations — just do the work directly.";
18
+ const stateEntry = "goal.state";
19
+ const goalSchema = Type.Object({
20
+ id: Type.String({ description: "Stable id of the goal to create or update" }),
21
+ title: Type.String({ description: "Human-readable goal title" }),
22
+ revision: Type.Integer({
23
+ description: "New revision counter; must be greater than the stored revision of an existing goal",
24
+ minimum: 0,
25
+ }),
26
+ status: Type.Optional(Type.Union([Type.Literal("open"), Type.Literal("completed"), Type.Literal("cancelled")], {
27
+ description: "Next status; defaults to the stored status for existing goals, otherwise open",
28
+ })),
29
+ evidence: Type.Optional(Type.Array(Type.String(), {
30
+ description: "Evidence references; at least one is required when status is completed",
31
+ })),
32
+ });
33
+ function objectInput(first, second) {
34
+ const value = second === undefined ? first : second;
35
+ if (!value || typeof value !== "object" || Array.isArray(value))
36
+ throw new Error("Goal input must be an object");
37
+ return value;
38
+ }
39
+ function text(value, name) {
40
+ if (typeof value !== "string" || value.trim() === "")
41
+ throw new Error(`${name} must be a non-empty string`);
42
+ return value;
43
+ }
44
+ function revision(value) {
45
+ if (!Number.isInteger(value) || value < 0)
46
+ throw new Error("revision must be a non-negative integer");
47
+ return value;
48
+ }
49
+ function evidence(value) {
50
+ if (value === undefined)
51
+ return [];
52
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
53
+ throw new Error("evidence must be a string array");
54
+ }
55
+ return [...value];
56
+ }
57
+ function loadState(api) {
58
+ const entries = api.session?.getBranchEntries() ?? [];
59
+ const entry = [...entries].reverse().find((item) => item.customType === stateEntry);
60
+ if (!entry?.data || typeof entry.data !== "object")
61
+ return { goals: [] };
62
+ const data = entry.data;
63
+ return { goals: Array.isArray(data.goals) ? [...data.goals] : [] };
64
+ }
65
+ export function createCapability(api) {
66
+ let state = loadState(api);
67
+ const persist = async () => {
68
+ api.session?.appendEntry(stateEntry, state);
69
+ await api.publish({ type: "goal.changed", version: 1, correlationId: stateEntry, data: state });
70
+ return state;
71
+ };
72
+ const tool = api.registerTool({
73
+ name: "goal_set",
74
+ label: "Set goal",
75
+ promptGuidelines: [TASK_TOOLS_GUIDE],
76
+ description: "Create or update a goal with explicit revision and evidence",
77
+ parameters: goalSchema,
78
+ execute: async (first, second) => {
79
+ const input = objectInput(first, second);
80
+ const id = text(input.id, "id");
81
+ const title = text(input.title, "title");
82
+ const nextRevision = revision(input.revision);
83
+ const current = state.goals.find((item) => item.id === id);
84
+ if (current && nextRevision <= current.revision)
85
+ throw new Error(`stale goal revision: ${id}`);
86
+ if (current?.status === "completed" || current?.status === "cancelled") {
87
+ throw new Error(`goal is terminal and cannot be updated: ${id}`);
88
+ }
89
+ const values = evidence(input.evidence);
90
+ const record = {
91
+ id,
92
+ title,
93
+ status: input.status === undefined
94
+ ? (current?.status ?? "open")
95
+ : text(input.status, "status"),
96
+ revision: nextRevision,
97
+ evidence: values,
98
+ };
99
+ if (!["open", "completed", "cancelled"].includes(record.status))
100
+ throw new Error("invalid goal status");
101
+ if (record.status === "completed" && values.length === 0) {
102
+ throw new Error("goal completion requires at least one evidence reference");
103
+ }
104
+ state = { ...state, goals: [...state.goals.filter((item) => item.id !== id), record] };
105
+ return persist();
106
+ },
107
+ });
108
+ return [tool];
109
+ }
110
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Goal capability plugin: a self-contained goal state machine, extracted from\n * the host's `src/capabilities/goal.ts` (D-075 S4, first B-class batch).\n *\n * Goals persist as append-only `goal.state` session entries and every change\n * is announced as a `goal.changed` event. The plugin only consumes the public\n * `session`, `events`, and `tools` capabilities, keeps soft string references\n * to other domains, and never validates state owned by another plugin.\n */\n\nimport { Type } from \"typebox\";\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\n\n/**\n * Task tools guide(任务工具引导 #4,原 system-prompt.ts TASK_TOOL_GUIDE,D-071\n * 迁移):tells the model WHEN to use the goal/plan/todo face. It rides\n * goal_set's promptGuidelines so the text lives with the plugin that owns the\n * tools (宪法 §3) and renders whenever the tool face is active.\n */\nexport const TASK_TOOLS_GUIDE =\n\t\"Task Management: For multi-step tasks that span multiple turns, use goal_set to define the objective, plan_set to outline the approach, and todo_update to track progress. Skip these tools for simple one-step operations — just do the work directly.\";\n\nexport interface GoalRecord {\n\tid: string;\n\ttitle: string;\n\tstatus: \"open\" | \"completed\" | \"cancelled\";\n\trevision: number;\n\tevidence: string[];\n}\n\nexport interface GoalState {\n\tgoals: GoalRecord[];\n}\n\nconst stateEntry = \"goal.state\";\n\nconst goalSchema = Type.Object({\n\tid: Type.String({ description: \"Stable id of the goal to create or update\" }),\n\ttitle: Type.String({ description: \"Human-readable goal title\" }),\n\trevision: Type.Integer({\n\t\tdescription: \"New revision counter; must be greater than the stored revision of an existing goal\",\n\t\tminimum: 0,\n\t}),\n\tstatus: Type.Optional(\n\t\tType.Union([Type.Literal(\"open\"), Type.Literal(\"completed\"), Type.Literal(\"cancelled\")], {\n\t\t\tdescription: \"Next status; defaults to the stored status for existing goals, otherwise open\",\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(\"Goal 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): GoalState {\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 { goals: [] };\n\tconst data = entry.data as Partial<GoalState>;\n\treturn { goals: Array.isArray(data.goals) ? [...data.goals] : [] };\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<GoalState> => {\n\t\tapi.session?.appendEntry(stateEntry, state);\n\t\tawait api.publish({ type: \"goal.changed\", version: 1, correlationId: stateEntry, data: state });\n\t\treturn state;\n\t};\n\n\tconst tool = api.registerTool({\n\t\tname: \"goal_set\",\n\t\tlabel: \"Set goal\",\n\t\tpromptGuidelines: [TASK_TOOLS_GUIDE],\n\t\tdescription: \"Create or update a goal with explicit revision and evidence\",\n\t\tparameters: goalSchema,\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 title = text(input.title, \"title\");\n\t\t\tconst nextRevision = revision(input.revision);\n\t\t\tconst current = state.goals.find((item) => item.id === id);\n\t\t\tif (current && nextRevision <= current.revision) throw new Error(`stale goal revision: ${id}`);\n\t\t\tif (current?.status === \"completed\" || current?.status === \"cancelled\") {\n\t\t\t\tthrow new Error(`goal is terminal and cannot be updated: ${id}`);\n\t\t\t}\n\t\t\tconst values = evidence(input.evidence);\n\t\t\tconst record: GoalRecord = {\n\t\t\t\tid,\n\t\t\t\ttitle,\n\t\t\t\tstatus:\n\t\t\t\t\tinput.status === undefined\n\t\t\t\t\t\t? (current?.status ?? \"open\")\n\t\t\t\t\t\t: (text(input.status, \"status\") as GoalRecord[\"status\"]),\n\t\t\t\trevision: nextRevision,\n\t\t\t\tevidence: values,\n\t\t\t};\n\t\t\tif (![\"open\", \"completed\", \"cancelled\"].includes(record.status)) throw new Error(\"invalid goal status\");\n\t\t\tif (record.status === \"completed\" && values.length === 0) {\n\t\t\t\tthrow new Error(\"goal completion requires at least one evidence reference\");\n\t\t\t}\n\t\t\tstate = { ...state, goals: [...state.goals.filter((item) => item.id !== id), record] };\n\t\t\treturn persist();\n\t\t},\n\t});\n\n\treturn [tool];\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@ai-agent-forge/plugin-goal",
3
+ "version": "0.85.0",
4
+ "description": "Goal capability plugin for Agent Forge (goal_set state machine over session entries)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "agent-forge.json",
17
+ "plugin.json",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "clean": "shx rm -rf dist",
22
+ "build": "tsgo -p tsconfig.build.json",
23
+ "test": "vitest --run",
24
+ "prepublishOnly": "npm run clean && npm run build"
25
+ },
26
+ "keywords": [
27
+ "agent-forge",
28
+ "plugin",
29
+ "goal"
30
+ ],
31
+ "author": "Agent Forge contributors",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/agent-forge/agent-forge.git",
36
+ "directory": "plugins/goal"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/agent-forge/agent-forge/issues"
40
+ },
41
+ "homepage": "https://github.com/agent-forge/agent-forge#readme",
42
+ "engines": {
43
+ "node": ">=22.19.0"
44
+ },
45
+ "dependencies": {
46
+ "@agent-forge/plugin-sdk": "^0.85.0",
47
+ "typebox": "1.3.27"
48
+ },
49
+ "devDependencies": {
50
+ "@agent-forge/agent-forge": "^0.85.0",
51
+ "shx": "0.4.0",
52
+ "vitest": "4.1.9"
53
+ }
54
+ }
package/plugin.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "agent-forge.plugin.goal",
3
+ "version": "0.84.4",
4
+ "apiVersion": "1-draft",
5
+ "entry": "./dist/entry.js",
6
+ "hostVersion": ">=0.84.4",
7
+ "requiredCapabilities": ["session", "events", "tools"]
8
+ }