@ai-agent-forge/plugin-goal 0.88.15 → 0.88.17

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,7 +1,7 @@
1
1
  {
2
2
  "packageFormatVersion": 1,
3
3
  "id": "agent-forge.plugin.goal",
4
- "pluginVersion": "0.88.15",
4
+ "pluginVersion": "0.88.17",
5
5
  "apiVersion": "1",
6
6
  "minHostVersion": "0.84.4",
7
7
  "platform": {
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ import type { CapabilityAPI, DisposableRegistration } from "@agent-forge/plugin-
14
14
  * goal_set's promptGuidelines so the text lives with the plugin that owns the
15
15
  * tools (宪法 §3) and renders whenever the tool face is active.
16
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. Match the tooling to the task's weight: sesame-sized work (a one-line edit, moving a file, anything finished in about a minute) gets no goal/plan/todo at all \u2014 process overhead must stay smaller than the work itself, or the experience degrades. If the user explicitly asks for one of these tools and the task genuinely does not need it, skipping is fine, but say so in one line instead of silently ignoring the request. Steering mid-task: when the user sends input while unfinished goal/plan/todo records exist, classify it first \u2014 background (no state change; comply in execution), add (new work: new todo ids; plans only when the work outgrows the existing plan's route), modify (unfinished items: update the same record with a higher revision), cancel (only what the user's input actually retires, including skip-this-one requests: todo cancelled, with the steering line as its evidence \u2014 never fake done, never cancel on your own initiative, never park a retired item as blocked; plan/goal cancelled), pivot (the objective changed: settle workflow-level consequences first, then revise or cancel goal/plan, todos last; workflows follow their correction routing), or a user fork (risk L1+ and the user's preference cannot be derived from existing instructions: ask via the decision channel instead of deciding unilaterally). Then settle it in the same turn with the task tools \u2014 state changes, not just words; never batch these cross-domain writes. Touch only affected records; finished records are history (rework = a new item citing the old id); when unsure whether the user meant a list change, change nothing and say so.";
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. Match the tooling to the task's weight: sesame-sized work (a one-line edit, moving a file, anything finished in about a minute) gets no goal/plan/todo at all \u2014 process overhead must stay smaller than the work itself, or the experience degrades. If the user explicitly asks for one of these tools and the task genuinely does not need it, skipping is fine, but say so in one line instead of silently ignoring the request. Terminal states are final: a todo marked done or cancelled, a completed or cancelled plan, and a completed or cancelled goal can never be updated again \u2014 not even to append evidence. So do the work and commit it before marking anything done, and write every evidence reference (including the commit id) in that same completion call; there is no later chance to add one. Steering mid-task: when the user sends input while unfinished goal/plan/todo records exist, classify it first \u2014 background (no state change; comply in execution), add (new work: new todo ids; plans only when the work outgrows the existing plan's route), modify (unfinished items: update the same record with a higher revision), cancel (only what the user's input actually retires, including skip-this-one requests: todo cancelled, with the steering line as its evidence \u2014 never fake done, never cancel on your own initiative, never park a retired item as blocked; plan/goal cancelled), pivot (the objective changed: settle workflow-level consequences first, then revise or cancel goal/plan, todos last; workflows follow their correction routing), or a user fork (risk L1+ and the user's preference cannot be derived from existing instructions: ask via the decision channel instead of deciding unilaterally). Then settle it in the same turn with the task tools \u2014 state changes, not just words; never batch these cross-domain writes. Touch only affected records; finished records are history (rework = a new item citing the old id); when unsure whether the user meant a list change, change nothing and say so.";
18
18
  export interface GoalRecord {
19
19
  id: string;
20
20
  title: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGrF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,+xDAC4uD,CAAC;AAE1wD,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;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAa1F;AAiLD,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,aAAa,GAChB,OAAO,CAAC,SAAS,sBAAsB,EAAE,CAAC,GAAG,SAAS,sBAAsB,EAAE,CAkEhF","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 { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\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. Match the tooling to the task's weight: sesame-sized work (a one-line edit, moving a file, anything finished in about a minute) gets no goal/plan/todo at all — process overhead must stay smaller than the work itself, or the experience degrades. If the user explicitly asks for one of these tools and the task genuinely does not need it, skipping is fine, but say so in one line instead of silently ignoring the request. Steering mid-task: when the user sends input while unfinished goal/plan/todo records exist, classify it first — background (no state change; comply in execution), add (new work: new todo ids; plans only when the work outgrows the existing plan's route), modify (unfinished items: update the same record with a higher revision), cancel (only what the user's input actually retires, including skip-this-one requests: todo cancelled, with the steering line as its evidence — never fake done, never cancel on your own initiative, never park a retired item as blocked; plan/goal cancelled), pivot (the objective changed: settle workflow-level consequences first, then revise or cancel goal/plan, todos last; workflows follow their correction routing), or a user fork (risk L1+ and the user's preference cannot be derived from existing instructions: ask via the decision channel instead of deciding unilaterally). Then settle it in the same turn with the task tools — state changes, not just words; never batch these cross-domain writes. Touch only affected records; finished records are history (rework = a new item citing the old id); when unsure whether the user meant a list change, change nothing and say so.\";\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 = first !== null && typeof first === \"object\" && !Array.isArray(first) ? 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\n/**\n * `/goal` command text rendering: one line per goal (`id [status] title`)\n * plus the evidence lines that justified completion. Plain text only —\n * every host shape can render it, no TUI required (the same L1 contract as\n * the workflow plugin's `/workflow`). Collapsed tool blocks pass\n * `{ evidence: false }` to omit the evidence lines; the default keeps the\n * command output byte-for-byte stable.\n */\nexport function renderGoalState(state: GoalState, options?: { evidence?: boolean }): string {\n\tconst withEvidence = options?.evidence ?? true;\n\tif (state.goals.length === 0) return \"no goals in this session; create one with goal_set\";\n\tconst open = state.goals.filter((goal) => goal.status === \"open\").length;\n\tconst lines = [`goals: ${state.goals.length} (${open} open)`];\n\t// Record fields only validate non-emptiness, so fold newlines to keep the\n\t// one-line-per-record shape whatever the model wrote.\n\tconst inline = (value: string): string => value.replace(/\\n/g, \" \");\n\tfor (const goal of state.goals) {\n\t\tlines.push(`- ${inline(goal.id)} [${goal.status}] ${inline(goal.title)}`);\n\t\tif (withEvidence) for (const item of goal.evidence) lines.push(` evidence: ${inline(item)}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Structural slices of the host tool-render contracts the renderer needs.\n * Declared locally on purpose: this plugin must stay self-contained and must\n * not import `@agent-forge/tui` (the tool-definition.ts structural hosting\n * boundary), so renderResult talks to these shapes instead of the real\n * Theme/Component types.\n */\ninterface ToolResultTextPartLike {\n\treadonly type: string;\n\treadonly text?: string;\n}\n\ninterface ToolResultLike {\n\treadonly content: readonly ToolResultTextPartLike[];\n}\n\ninterface RenderResultOptionsLike {\n\treadonly expanded: boolean;\n\treadonly isPartial: boolean;\n}\n\ninterface ThemeLike {\n\tfg(color: string, text: string): string;\n}\n\ninterface RenderContextLike {\n\treadonly lastComponent?: unknown;\n}\n\n/**\n * Simplified East Asian width table: these ranges cover the CJK blocks state\n * titles realistically use and count as 2 columns each; every other code\n * point counts as 1. Not a full wcwidth — combining marks and ambiguous\n * widths fall back to 1, the safe (narrower) default for row folding.\n */\nfunction isWideCodePoint(code: number): boolean {\n\treturn (\n\t\t(code >= 0x1100 && code <= 0x115f) ||\n\t\t(code >= 0x2e80 && code <= 0xa4cf) ||\n\t\t(code >= 0xac00 && code <= 0xd7a3) ||\n\t\t(code >= 0xf900 && code <= 0xfaff) ||\n\t\t(code >= 0xfe30 && code <= 0xfe4f) ||\n\t\t(code >= 0xff00 && code <= 0xff60) ||\n\t\t(code >= 0xffe0 && code <= 0xffe6) ||\n\t\t(code >= 0x20000 && code <= 0x3fffd)\n\t);\n}\n\n/** Folds one logical line into rows of at most `width` display columns. */\nfunction wrapLine(line: string, width: number): string[] {\n\tif (width <= 0) return [line];\n\tconst rows: string[] = [];\n\tlet row = \"\";\n\tlet columns = 0;\n\tfor (const character of line) {\n\t\tconst cost = isWideCodePoint(character.codePointAt(0) ?? 0) ? 2 : 1;\n\t\tif (row !== \"\" && columns + cost > width) {\n\t\t\trows.push(row);\n\t\t\trow = character;\n\t\t\tcolumns = cost;\n\t\t} else {\n\t\t\trow += character;\n\t\t\tcolumns += cost;\n\t\t}\n\t}\n\trows.push(row);\n\treturn rows;\n}\n\n/**\n * Structural TUI component rendering a goal_set result as the plain state\n * listing: `warning: ` lines take the host theme's warning color, every other\n * row toolOutput (color is decided per logical line, so a wrapped warning\n * stays warning-colored). Rows are cached per width — the host may render the\n * same result repeatedly at one width — and dropped on text updates or\n * invalidate.\n */\nclass GoalResultComponent {\n\tprivate text: string;\n\tprivate readonly theme: ThemeLike;\n\tprivate readonly rowsByWidth = new Map<number, string[]>();\n\n\tconstructor(text: string, theme: ThemeLike) {\n\t\tthis.text = text;\n\t\tthis.theme = theme;\n\t}\n\n\tsetText(text: string): void {\n\t\tif (text === this.text) return;\n\t\tthis.text = text;\n\t\tthis.rowsByWidth.clear();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst cached = this.rowsByWidth.get(width);\n\t\tif (cached) return cached;\n\t\tconst rows: string[] = [];\n\t\tfor (const line of this.text.split(\"\\n\")) {\n\t\t\tconst color = line.startsWith(\"warning: \") ? \"warning\" : \"toolOutput\";\n\t\t\tfor (const row of wrapLine(line, width)) rows.push(this.theme.fg(color, row));\n\t\t}\n\t\tthis.rowsByWidth.set(width, rows);\n\t\treturn rows;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.rowsByWidth.clear();\n\t}\n}\n\n/**\n * Parses the text part the host serialized from this plugin's execute return\n * (capability-runtime-guards normalization): a `GoalState`. Anything else —\n * a JSON.parse failure or a different shape — returns undefined so the\n * renderer falls back to the raw text.\n */\nfunction parseGoalResultText(text: string): { state: GoalState; warning?: string } | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n\tconst record = parsed as { goals?: unknown; warning?: unknown };\n\tif (!Array.isArray(record.goals)) return undefined;\n\tif (record.warning !== undefined && typeof record.warning !== \"string\") return undefined;\n\tconst state: GoalState = { goals: record.goals as GoalRecord[] };\n\treturn record.warning === undefined ? { state } : { state, warning: record.warning };\n}\n\n/**\n * renderResult for goal_set: re-renders the tool's own state payload as the\n * readable listing instead of the host's generic one-line JSON fallback.\n * Collapsed blocks show the summary and record lines only; expanded blocks\n * add the evidence lines; a `warning` field, if a response ever carries one,\n * rides along as a final warning-colored line. Non-state payloads and\n * malformed records render verbatim as toolOutput.\n */\nfunction renderGoalResult(\n\tresult: ToolResultLike,\n\toptions: RenderResultOptionsLike,\n\ttheme: ThemeLike,\n\tcontext: RenderContextLike,\n): GoalResultComponent | undefined {\n\tconst text = result.content.find((part) => part.type === \"text\")?.text;\n\tif (text === undefined || text === \"\") return undefined;\n\tconst parsed = parseGoalResultText(text);\n\tlet listing: string;\n\tif (parsed) {\n\t\t// The parse gate is envelope-level, so a parsed state may still hold\n\t\t// malformed records (null elements, a record missing evidence).\n\t\t// Rendering those must not throw out of the plugin: fall back to the\n\t\t// raw text, the same path as a parse failure.\n\t\ttry {\n\t\t\tlisting = renderGoalState(parsed.state, { evidence: options.expanded });\n\t\t\tif (parsed.warning !== undefined) {\n\t\t\t\t// Fold newlines like the record fields so the warning stays one line.\n\t\t\t\tlisting = `${listing}\\nwarning: ${parsed.warning.replace(/\\n/g, \" \")}`;\n\t\t\t}\n\t\t} catch {\n\t\t\tlisting = text;\n\t\t}\n\t} else {\n\t\tlisting = text;\n\t}\n\t// Reuse the host-provided component for this render slot (the ls.ts\n\t// precedent): same instance in, same instance out, with fresh text.\n\tif (context.lastComponent instanceof GoalResultComponent) {\n\t\tcontext.lastComponent.setText(listing);\n\t\treturn context.lastComponent;\n\t}\n\treturn new GoalResultComponent(listing, theme);\n}\n\nexport function createCapability(\n\tapi: CapabilityAPI,\n): Promise<readonly DisposableRegistration[]> | readonly DisposableRegistration[] {\n\tlet state = loadState(api);\n\tconst refreshState = (): GoalState => {\n\t\tif (api.session) state = loadState(api);\n\t\treturn state;\n\t};\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\trenderResult: renderGoalResult,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\trefreshState();\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\tconst command = api.registerCommand({\n\t\tname: \"goal\",\n\t\tdescription: \"Show this session's goals (status and evidence)\",\n\t\texecute: async (args: string) => {\n\t\t\ttry {\n\t\t\t\tif (args.trim() !== \"\") return { status: \"error\" as const, message: \"Usage: /goal\" };\n\t\t\t\treturn { status: \"success\" as const, message: renderGoalState(refreshState()) };\n\t\t\t} catch (error) {\n\t\t\t\t// A slash command must not crash the host (memory /promote precedent).\n\t\t\t\treturn { status: \"error\" as const, message: error instanceof Error ? error.message : String(error) };\n\t\t\t}\n\t\t},\n\t});\n\n\treturn [tool, command];\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGrF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,spEAC8lE,CAAC;AAE5nE,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;AAyDD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAa1F;AAiLD,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,aAAa,GAChB,OAAO,CAAC,SAAS,sBAAsB,EAAE,CAAC,GAAG,SAAS,sBAAsB,EAAE,CAkEhF","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 { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\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. Match the tooling to the task's weight: sesame-sized work (a one-line edit, moving a file, anything finished in about a minute) gets no goal/plan/todo at all — process overhead must stay smaller than the work itself, or the experience degrades. If the user explicitly asks for one of these tools and the task genuinely does not need it, skipping is fine, but say so in one line instead of silently ignoring the request. Terminal states are final: a todo marked done or cancelled, a completed or cancelled plan, and a completed or cancelled goal can never be updated again — not even to append evidence. So do the work and commit it before marking anything done, and write every evidence reference (including the commit id) in that same completion call; there is no later chance to add one. Steering mid-task: when the user sends input while unfinished goal/plan/todo records exist, classify it first — background (no state change; comply in execution), add (new work: new todo ids; plans only when the work outgrows the existing plan's route), modify (unfinished items: update the same record with a higher revision), cancel (only what the user's input actually retires, including skip-this-one requests: todo cancelled, with the steering line as its evidence — never fake done, never cancel on your own initiative, never park a retired item as blocked; plan/goal cancelled), pivot (the objective changed: settle workflow-level consequences first, then revise or cancel goal/plan, todos last; workflows follow their correction routing), or a user fork (risk L1+ and the user's preference cannot be derived from existing instructions: ask via the decision channel instead of deciding unilaterally). Then settle it in the same turn with the task tools — state changes, not just words; never batch these cross-domain writes. Touch only affected records; finished records are history (rework = a new item citing the old id); when unsure whether the user meant a list change, change nothing and say so.\";\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:\n\t\t\t\t\"Next status; defaults to the stored status for existing goals, otherwise open. completed and cancelled are terminal: a goal holding either status can never be updated again\",\n\t\t}),\n\t),\n\tevidence: Type.Optional(\n\t\tType.Array(Type.String(), {\n\t\t\tdescription:\n\t\t\t\t\"Evidence references. This list REPLACES the stored list on every call — re-send every existing reference when updating. At least one is required when status is completed; the list is final once the goal is completed or cancelled and can never change\",\n\t\t}),\n\t),\n});\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? 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\n/**\n * `/goal` command text rendering: one line per goal (`id [status] title`)\n * plus the evidence lines that justified completion. Plain text only —\n * every host shape can render it, no TUI required (the same L1 contract as\n * the workflow plugin's `/workflow`). Collapsed tool blocks pass\n * `{ evidence: false }` to omit the evidence lines; the default keeps the\n * command output byte-for-byte stable.\n */\nexport function renderGoalState(state: GoalState, options?: { evidence?: boolean }): string {\n\tconst withEvidence = options?.evidence ?? true;\n\tif (state.goals.length === 0) return \"no goals in this session; create one with goal_set\";\n\tconst open = state.goals.filter((goal) => goal.status === \"open\").length;\n\tconst lines = [`goals: ${state.goals.length} (${open} open)`];\n\t// Record fields only validate non-emptiness, so fold newlines to keep the\n\t// one-line-per-record shape whatever the model wrote.\n\tconst inline = (value: string): string => value.replace(/\\n/g, \" \");\n\tfor (const goal of state.goals) {\n\t\tlines.push(`- ${inline(goal.id)} [${goal.status}] ${inline(goal.title)}`);\n\t\tif (withEvidence) for (const item of goal.evidence) lines.push(` evidence: ${inline(item)}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Structural slices of the host tool-render contracts the renderer needs.\n * Declared locally on purpose: this plugin must stay self-contained and must\n * not import `@agent-forge/tui` (the tool-definition.ts structural hosting\n * boundary), so renderResult talks to these shapes instead of the real\n * Theme/Component types.\n */\ninterface ToolResultTextPartLike {\n\treadonly type: string;\n\treadonly text?: string;\n}\n\ninterface ToolResultLike {\n\treadonly content: readonly ToolResultTextPartLike[];\n}\n\ninterface RenderResultOptionsLike {\n\treadonly expanded: boolean;\n\treadonly isPartial: boolean;\n}\n\ninterface ThemeLike {\n\tfg(color: string, text: string): string;\n}\n\ninterface RenderContextLike {\n\treadonly lastComponent?: unknown;\n}\n\n/**\n * Simplified East Asian width table: these ranges cover the CJK blocks state\n * titles realistically use and count as 2 columns each; every other code\n * point counts as 1. Not a full wcwidth — combining marks and ambiguous\n * widths fall back to 1, the safe (narrower) default for row folding.\n */\nfunction isWideCodePoint(code: number): boolean {\n\treturn (\n\t\t(code >= 0x1100 && code <= 0x115f) ||\n\t\t(code >= 0x2e80 && code <= 0xa4cf) ||\n\t\t(code >= 0xac00 && code <= 0xd7a3) ||\n\t\t(code >= 0xf900 && code <= 0xfaff) ||\n\t\t(code >= 0xfe30 && code <= 0xfe4f) ||\n\t\t(code >= 0xff00 && code <= 0xff60) ||\n\t\t(code >= 0xffe0 && code <= 0xffe6) ||\n\t\t(code >= 0x20000 && code <= 0x3fffd)\n\t);\n}\n\n/** Folds one logical line into rows of at most `width` display columns. */\nfunction wrapLine(line: string, width: number): string[] {\n\tif (width <= 0) return [line];\n\tconst rows: string[] = [];\n\tlet row = \"\";\n\tlet columns = 0;\n\tfor (const character of line) {\n\t\tconst cost = isWideCodePoint(character.codePointAt(0) ?? 0) ? 2 : 1;\n\t\tif (row !== \"\" && columns + cost > width) {\n\t\t\trows.push(row);\n\t\t\trow = character;\n\t\t\tcolumns = cost;\n\t\t} else {\n\t\t\trow += character;\n\t\t\tcolumns += cost;\n\t\t}\n\t}\n\trows.push(row);\n\treturn rows;\n}\n\n/**\n * Structural TUI component rendering a goal_set result as the plain state\n * listing: `warning: ` lines take the host theme's warning color, every other\n * row toolOutput (color is decided per logical line, so a wrapped warning\n * stays warning-colored). Rows are cached per width — the host may render the\n * same result repeatedly at one width — and dropped on text updates or\n * invalidate.\n */\nclass GoalResultComponent {\n\tprivate text: string;\n\tprivate readonly theme: ThemeLike;\n\tprivate readonly rowsByWidth = new Map<number, string[]>();\n\n\tconstructor(text: string, theme: ThemeLike) {\n\t\tthis.text = text;\n\t\tthis.theme = theme;\n\t}\n\n\tsetText(text: string): void {\n\t\tif (text === this.text) return;\n\t\tthis.text = text;\n\t\tthis.rowsByWidth.clear();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst cached = this.rowsByWidth.get(width);\n\t\tif (cached) return cached;\n\t\tconst rows: string[] = [];\n\t\tfor (const line of this.text.split(\"\\n\")) {\n\t\t\tconst color = line.startsWith(\"warning: \") ? \"warning\" : \"toolOutput\";\n\t\t\tfor (const row of wrapLine(line, width)) rows.push(this.theme.fg(color, row));\n\t\t}\n\t\tthis.rowsByWidth.set(width, rows);\n\t\treturn rows;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.rowsByWidth.clear();\n\t}\n}\n\n/**\n * Parses the text part the host serialized from this plugin's execute return\n * (capability-runtime-guards normalization): a `GoalState`. Anything else —\n * a JSON.parse failure or a different shape — returns undefined so the\n * renderer falls back to the raw text.\n */\nfunction parseGoalResultText(text: string): { state: GoalState; warning?: string } | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n\tconst record = parsed as { goals?: unknown; warning?: unknown };\n\tif (!Array.isArray(record.goals)) return undefined;\n\tif (record.warning !== undefined && typeof record.warning !== \"string\") return undefined;\n\tconst state: GoalState = { goals: record.goals as GoalRecord[] };\n\treturn record.warning === undefined ? { state } : { state, warning: record.warning };\n}\n\n/**\n * renderResult for goal_set: re-renders the tool's own state payload as the\n * readable listing instead of the host's generic one-line JSON fallback.\n * Collapsed blocks show the summary and record lines only; expanded blocks\n * add the evidence lines; a `warning` field, if a response ever carries one,\n * rides along as a final warning-colored line. Non-state payloads and\n * malformed records render verbatim as toolOutput.\n */\nfunction renderGoalResult(\n\tresult: ToolResultLike,\n\toptions: RenderResultOptionsLike,\n\ttheme: ThemeLike,\n\tcontext: RenderContextLike,\n): GoalResultComponent | undefined {\n\tconst text = result.content.find((part) => part.type === \"text\")?.text;\n\tif (text === undefined || text === \"\") return undefined;\n\tconst parsed = parseGoalResultText(text);\n\tlet listing: string;\n\tif (parsed) {\n\t\t// The parse gate is envelope-level, so a parsed state may still hold\n\t\t// malformed records (null elements, a record missing evidence).\n\t\t// Rendering those must not throw out of the plugin: fall back to the\n\t\t// raw text, the same path as a parse failure.\n\t\ttry {\n\t\t\tlisting = renderGoalState(parsed.state, { evidence: options.expanded });\n\t\t\tif (parsed.warning !== undefined) {\n\t\t\t\t// Fold newlines like the record fields so the warning stays one line.\n\t\t\t\tlisting = `${listing}\\nwarning: ${parsed.warning.replace(/\\n/g, \" \")}`;\n\t\t\t}\n\t\t} catch {\n\t\t\tlisting = text;\n\t\t}\n\t} else {\n\t\tlisting = text;\n\t}\n\t// Reuse the host-provided component for this render slot (the ls.ts\n\t// precedent): same instance in, same instance out, with fresh text.\n\tif (context.lastComponent instanceof GoalResultComponent) {\n\t\tcontext.lastComponent.setText(listing);\n\t\treturn context.lastComponent;\n\t}\n\treturn new GoalResultComponent(listing, theme);\n}\n\nexport function createCapability(\n\tapi: CapabilityAPI,\n): Promise<readonly DisposableRegistration[]> | readonly DisposableRegistration[] {\n\tlet state = loadState(api);\n\tconst refreshState = (): GoalState => {\n\t\tif (api.session) state = loadState(api);\n\t\treturn state;\n\t};\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\trenderResult: renderGoalResult,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\trefreshState();\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\tconst command = api.registerCommand({\n\t\tname: \"goal\",\n\t\tdescription: \"Show this session's goals (status and evidence)\",\n\t\texecute: async (args: string) => {\n\t\t\ttry {\n\t\t\t\tif (args.trim() !== \"\") return { status: \"error\" as const, message: \"Usage: /goal\" };\n\t\t\t\treturn { status: \"success\" as const, message: renderGoalState(refreshState()) };\n\t\t\t} catch (error) {\n\t\t\t\t// A slash command must not crash the host (memory /promote precedent).\n\t\t\t\treturn { status: \"error\" as const, message: error instanceof Error ? error.message : String(error) };\n\t\t\t}\n\t\t},\n\t});\n\n\treturn [tool, command];\n}\n"]}
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import { Type } from "typebox";
14
14
  * goal_set's promptGuidelines so the text lives with the plugin that owns the
15
15
  * tools (宪法 §3) and renders whenever the tool face is active.
16
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. Match the tooling to the task's weight: sesame-sized work (a one-line edit, moving a file, anything finished in about a minute) gets no goal/plan/todo at all — process overhead must stay smaller than the work itself, or the experience degrades. If the user explicitly asks for one of these tools and the task genuinely does not need it, skipping is fine, but say so in one line instead of silently ignoring the request. Steering mid-task: when the user sends input while unfinished goal/plan/todo records exist, classify it first — background (no state change; comply in execution), add (new work: new todo ids; plans only when the work outgrows the existing plan's route), modify (unfinished items: update the same record with a higher revision), cancel (only what the user's input actually retires, including skip-this-one requests: todo cancelled, with the steering line as its evidence — never fake done, never cancel on your own initiative, never park a retired item as blocked; plan/goal cancelled), pivot (the objective changed: settle workflow-level consequences first, then revise or cancel goal/plan, todos last; workflows follow their correction routing), or a user fork (risk L1+ and the user's preference cannot be derived from existing instructions: ask via the decision channel instead of deciding unilaterally). Then settle it in the same turn with the task tools — state changes, not just words; never batch these cross-domain writes. Touch only affected records; finished records are history (rework = a new item citing the old id); when unsure whether the user meant a list change, change nothing and say so.";
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. Match the tooling to the task's weight: sesame-sized work (a one-line edit, moving a file, anything finished in about a minute) gets no goal/plan/todo at all — process overhead must stay smaller than the work itself, or the experience degrades. If the user explicitly asks for one of these tools and the task genuinely does not need it, skipping is fine, but say so in one line instead of silently ignoring the request. Terminal states are final: a todo marked done or cancelled, a completed or cancelled plan, and a completed or cancelled goal can never be updated again — not even to append evidence. So do the work and commit it before marking anything done, and write every evidence reference (including the commit id) in that same completion call; there is no later chance to add one. Steering mid-task: when the user sends input while unfinished goal/plan/todo records exist, classify it first — background (no state change; comply in execution), add (new work: new todo ids; plans only when the work outgrows the existing plan's route), modify (unfinished items: update the same record with a higher revision), cancel (only what the user's input actually retires, including skip-this-one requests: todo cancelled, with the steering line as its evidence — never fake done, never cancel on your own initiative, never park a retired item as blocked; plan/goal cancelled), pivot (the objective changed: settle workflow-level consequences first, then revise or cancel goal/plan, todos last; workflows follow their correction routing), or a user fork (risk L1+ and the user's preference cannot be derived from existing instructions: ask via the decision channel instead of deciding unilaterally). Then settle it in the same turn with the task tools — state changes, not just words; never batch these cross-domain writes. Touch only affected records; finished records are history (rework = a new item citing the old id); when unsure whether the user meant a list change, change nothing and say so.";
18
18
  const stateEntry = "goal.state";
19
19
  const goalSchema = Type.Object({
20
20
  id: Type.String({ description: "Stable id of the goal to create or update" }),
@@ -24,10 +24,10 @@ const goalSchema = Type.Object({
24
24
  minimum: 0,
25
25
  }),
26
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",
27
+ description: "Next status; defaults to the stored status for existing goals, otherwise open. completed and cancelled are terminal: a goal holding either status can never be updated again",
28
28
  })),
29
29
  evidence: Type.Optional(Type.Array(Type.String(), {
30
- description: "Evidence references; at least one is required when status is completed",
30
+ description: "Evidence references. This list REPLACES the stored list on every call — re-send every existing reference when updating. At least one is required when status is completed; the list is final once the goal is completed or cancelled and can never change",
31
31
  })),
32
32
  });
33
33
  function objectInput(first, second) {
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 * 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 { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\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. Match the tooling to the task's weight: sesame-sized work (a one-line edit, moving a file, anything finished in about a minute) gets no goal/plan/todo at all — process overhead must stay smaller than the work itself, or the experience degrades. If the user explicitly asks for one of these tools and the task genuinely does not need it, skipping is fine, but say so in one line instead of silently ignoring the request. Steering mid-task: when the user sends input while unfinished goal/plan/todo records exist, classify it first — background (no state change; comply in execution), add (new work: new todo ids; plans only when the work outgrows the existing plan's route), modify (unfinished items: update the same record with a higher revision), cancel (only what the user's input actually retires, including skip-this-one requests: todo cancelled, with the steering line as its evidence — never fake done, never cancel on your own initiative, never park a retired item as blocked; plan/goal cancelled), pivot (the objective changed: settle workflow-level consequences first, then revise or cancel goal/plan, todos last; workflows follow their correction routing), or a user fork (risk L1+ and the user's preference cannot be derived from existing instructions: ask via the decision channel instead of deciding unilaterally). Then settle it in the same turn with the task tools — state changes, not just words; never batch these cross-domain writes. Touch only affected records; finished records are history (rework = a new item citing the old id); when unsure whether the user meant a list change, change nothing and say so.\";\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 = first !== null && typeof first === \"object\" && !Array.isArray(first) ? 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\n/**\n * `/goal` command text rendering: one line per goal (`id [status] title`)\n * plus the evidence lines that justified completion. Plain text only —\n * every host shape can render it, no TUI required (the same L1 contract as\n * the workflow plugin's `/workflow`). Collapsed tool blocks pass\n * `{ evidence: false }` to omit the evidence lines; the default keeps the\n * command output byte-for-byte stable.\n */\nexport function renderGoalState(state: GoalState, options?: { evidence?: boolean }): string {\n\tconst withEvidence = options?.evidence ?? true;\n\tif (state.goals.length === 0) return \"no goals in this session; create one with goal_set\";\n\tconst open = state.goals.filter((goal) => goal.status === \"open\").length;\n\tconst lines = [`goals: ${state.goals.length} (${open} open)`];\n\t// Record fields only validate non-emptiness, so fold newlines to keep the\n\t// one-line-per-record shape whatever the model wrote.\n\tconst inline = (value: string): string => value.replace(/\\n/g, \" \");\n\tfor (const goal of state.goals) {\n\t\tlines.push(`- ${inline(goal.id)} [${goal.status}] ${inline(goal.title)}`);\n\t\tif (withEvidence) for (const item of goal.evidence) lines.push(` evidence: ${inline(item)}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Structural slices of the host tool-render contracts the renderer needs.\n * Declared locally on purpose: this plugin must stay self-contained and must\n * not import `@agent-forge/tui` (the tool-definition.ts structural hosting\n * boundary), so renderResult talks to these shapes instead of the real\n * Theme/Component types.\n */\ninterface ToolResultTextPartLike {\n\treadonly type: string;\n\treadonly text?: string;\n}\n\ninterface ToolResultLike {\n\treadonly content: readonly ToolResultTextPartLike[];\n}\n\ninterface RenderResultOptionsLike {\n\treadonly expanded: boolean;\n\treadonly isPartial: boolean;\n}\n\ninterface ThemeLike {\n\tfg(color: string, text: string): string;\n}\n\ninterface RenderContextLike {\n\treadonly lastComponent?: unknown;\n}\n\n/**\n * Simplified East Asian width table: these ranges cover the CJK blocks state\n * titles realistically use and count as 2 columns each; every other code\n * point counts as 1. Not a full wcwidth — combining marks and ambiguous\n * widths fall back to 1, the safe (narrower) default for row folding.\n */\nfunction isWideCodePoint(code: number): boolean {\n\treturn (\n\t\t(code >= 0x1100 && code <= 0x115f) ||\n\t\t(code >= 0x2e80 && code <= 0xa4cf) ||\n\t\t(code >= 0xac00 && code <= 0xd7a3) ||\n\t\t(code >= 0xf900 && code <= 0xfaff) ||\n\t\t(code >= 0xfe30 && code <= 0xfe4f) ||\n\t\t(code >= 0xff00 && code <= 0xff60) ||\n\t\t(code >= 0xffe0 && code <= 0xffe6) ||\n\t\t(code >= 0x20000 && code <= 0x3fffd)\n\t);\n}\n\n/** Folds one logical line into rows of at most `width` display columns. */\nfunction wrapLine(line: string, width: number): string[] {\n\tif (width <= 0) return [line];\n\tconst rows: string[] = [];\n\tlet row = \"\";\n\tlet columns = 0;\n\tfor (const character of line) {\n\t\tconst cost = isWideCodePoint(character.codePointAt(0) ?? 0) ? 2 : 1;\n\t\tif (row !== \"\" && columns + cost > width) {\n\t\t\trows.push(row);\n\t\t\trow = character;\n\t\t\tcolumns = cost;\n\t\t} else {\n\t\t\trow += character;\n\t\t\tcolumns += cost;\n\t\t}\n\t}\n\trows.push(row);\n\treturn rows;\n}\n\n/**\n * Structural TUI component rendering a goal_set result as the plain state\n * listing: `warning: ` lines take the host theme's warning color, every other\n * row toolOutput (color is decided per logical line, so a wrapped warning\n * stays warning-colored). Rows are cached per width — the host may render the\n * same result repeatedly at one width — and dropped on text updates or\n * invalidate.\n */\nclass GoalResultComponent {\n\tprivate text: string;\n\tprivate readonly theme: ThemeLike;\n\tprivate readonly rowsByWidth = new Map<number, string[]>();\n\n\tconstructor(text: string, theme: ThemeLike) {\n\t\tthis.text = text;\n\t\tthis.theme = theme;\n\t}\n\n\tsetText(text: string): void {\n\t\tif (text === this.text) return;\n\t\tthis.text = text;\n\t\tthis.rowsByWidth.clear();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst cached = this.rowsByWidth.get(width);\n\t\tif (cached) return cached;\n\t\tconst rows: string[] = [];\n\t\tfor (const line of this.text.split(\"\\n\")) {\n\t\t\tconst color = line.startsWith(\"warning: \") ? \"warning\" : \"toolOutput\";\n\t\t\tfor (const row of wrapLine(line, width)) rows.push(this.theme.fg(color, row));\n\t\t}\n\t\tthis.rowsByWidth.set(width, rows);\n\t\treturn rows;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.rowsByWidth.clear();\n\t}\n}\n\n/**\n * Parses the text part the host serialized from this plugin's execute return\n * (capability-runtime-guards normalization): a `GoalState`. Anything else —\n * a JSON.parse failure or a different shape — returns undefined so the\n * renderer falls back to the raw text.\n */\nfunction parseGoalResultText(text: string): { state: GoalState; warning?: string } | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n\tconst record = parsed as { goals?: unknown; warning?: unknown };\n\tif (!Array.isArray(record.goals)) return undefined;\n\tif (record.warning !== undefined && typeof record.warning !== \"string\") return undefined;\n\tconst state: GoalState = { goals: record.goals as GoalRecord[] };\n\treturn record.warning === undefined ? { state } : { state, warning: record.warning };\n}\n\n/**\n * renderResult for goal_set: re-renders the tool's own state payload as the\n * readable listing instead of the host's generic one-line JSON fallback.\n * Collapsed blocks show the summary and record lines only; expanded blocks\n * add the evidence lines; a `warning` field, if a response ever carries one,\n * rides along as a final warning-colored line. Non-state payloads and\n * malformed records render verbatim as toolOutput.\n */\nfunction renderGoalResult(\n\tresult: ToolResultLike,\n\toptions: RenderResultOptionsLike,\n\ttheme: ThemeLike,\n\tcontext: RenderContextLike,\n): GoalResultComponent | undefined {\n\tconst text = result.content.find((part) => part.type === \"text\")?.text;\n\tif (text === undefined || text === \"\") return undefined;\n\tconst parsed = parseGoalResultText(text);\n\tlet listing: string;\n\tif (parsed) {\n\t\t// The parse gate is envelope-level, so a parsed state may still hold\n\t\t// malformed records (null elements, a record missing evidence).\n\t\t// Rendering those must not throw out of the plugin: fall back to the\n\t\t// raw text, the same path as a parse failure.\n\t\ttry {\n\t\t\tlisting = renderGoalState(parsed.state, { evidence: options.expanded });\n\t\t\tif (parsed.warning !== undefined) {\n\t\t\t\t// Fold newlines like the record fields so the warning stays one line.\n\t\t\t\tlisting = `${listing}\\nwarning: ${parsed.warning.replace(/\\n/g, \" \")}`;\n\t\t\t}\n\t\t} catch {\n\t\t\tlisting = text;\n\t\t}\n\t} else {\n\t\tlisting = text;\n\t}\n\t// Reuse the host-provided component for this render slot (the ls.ts\n\t// precedent): same instance in, same instance out, with fresh text.\n\tif (context.lastComponent instanceof GoalResultComponent) {\n\t\tcontext.lastComponent.setText(listing);\n\t\treturn context.lastComponent;\n\t}\n\treturn new GoalResultComponent(listing, theme);\n}\n\nexport function createCapability(\n\tapi: CapabilityAPI,\n): Promise<readonly DisposableRegistration[]> | readonly DisposableRegistration[] {\n\tlet state = loadState(api);\n\tconst refreshState = (): GoalState => {\n\t\tif (api.session) state = loadState(api);\n\t\treturn state;\n\t};\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\trenderResult: renderGoalResult,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\trefreshState();\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\tconst command = api.registerCommand({\n\t\tname: \"goal\",\n\t\tdescription: \"Show this session's goals (status and evidence)\",\n\t\texecute: async (args: string) => {\n\t\t\ttry {\n\t\t\t\tif (args.trim() !== \"\") return { status: \"error\" as const, message: \"Usage: /goal\" };\n\t\t\t\treturn { status: \"success\" as const, message: renderGoalState(refreshState()) };\n\t\t\t} catch (error) {\n\t\t\t\t// A slash command must not crash the host (memory /promote precedent).\n\t\t\t\treturn { status: \"error\" as const, message: error instanceof Error ? error.message : String(error) };\n\t\t\t}\n\t\t},\n\t});\n\n\treturn [tool, command];\n}\n"]}
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 { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\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. Match the tooling to the task's weight: sesame-sized work (a one-line edit, moving a file, anything finished in about a minute) gets no goal/plan/todo at all — process overhead must stay smaller than the work itself, or the experience degrades. If the user explicitly asks for one of these tools and the task genuinely does not need it, skipping is fine, but say so in one line instead of silently ignoring the request. Terminal states are final: a todo marked done or cancelled, a completed or cancelled plan, and a completed or cancelled goal can never be updated again — not even to append evidence. So do the work and commit it before marking anything done, and write every evidence reference (including the commit id) in that same completion call; there is no later chance to add one. Steering mid-task: when the user sends input while unfinished goal/plan/todo records exist, classify it first — background (no state change; comply in execution), add (new work: new todo ids; plans only when the work outgrows the existing plan's route), modify (unfinished items: update the same record with a higher revision), cancel (only what the user's input actually retires, including skip-this-one requests: todo cancelled, with the steering line as its evidence — never fake done, never cancel on your own initiative, never park a retired item as blocked; plan/goal cancelled), pivot (the objective changed: settle workflow-level consequences first, then revise or cancel goal/plan, todos last; workflows follow their correction routing), or a user fork (risk L1+ and the user's preference cannot be derived from existing instructions: ask via the decision channel instead of deciding unilaterally). Then settle it in the same turn with the task tools — state changes, not just words; never batch these cross-domain writes. Touch only affected records; finished records are history (rework = a new item citing the old id); when unsure whether the user meant a list change, change nothing and say so.\";\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:\n\t\t\t\t\"Next status; defaults to the stored status for existing goals, otherwise open. completed and cancelled are terminal: a goal holding either status can never be updated again\",\n\t\t}),\n\t),\n\tevidence: Type.Optional(\n\t\tType.Array(Type.String(), {\n\t\t\tdescription:\n\t\t\t\t\"Evidence references. This list REPLACES the stored list on every call — re-send every existing reference when updating. At least one is required when status is completed; the list is final once the goal is completed or cancelled and can never change\",\n\t\t}),\n\t),\n});\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? 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\n/**\n * `/goal` command text rendering: one line per goal (`id [status] title`)\n * plus the evidence lines that justified completion. Plain text only —\n * every host shape can render it, no TUI required (the same L1 contract as\n * the workflow plugin's `/workflow`). Collapsed tool blocks pass\n * `{ evidence: false }` to omit the evidence lines; the default keeps the\n * command output byte-for-byte stable.\n */\nexport function renderGoalState(state: GoalState, options?: { evidence?: boolean }): string {\n\tconst withEvidence = options?.evidence ?? true;\n\tif (state.goals.length === 0) return \"no goals in this session; create one with goal_set\";\n\tconst open = state.goals.filter((goal) => goal.status === \"open\").length;\n\tconst lines = [`goals: ${state.goals.length} (${open} open)`];\n\t// Record fields only validate non-emptiness, so fold newlines to keep the\n\t// one-line-per-record shape whatever the model wrote.\n\tconst inline = (value: string): string => value.replace(/\\n/g, \" \");\n\tfor (const goal of state.goals) {\n\t\tlines.push(`- ${inline(goal.id)} [${goal.status}] ${inline(goal.title)}`);\n\t\tif (withEvidence) for (const item of goal.evidence) lines.push(` evidence: ${inline(item)}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Structural slices of the host tool-render contracts the renderer needs.\n * Declared locally on purpose: this plugin must stay self-contained and must\n * not import `@agent-forge/tui` (the tool-definition.ts structural hosting\n * boundary), so renderResult talks to these shapes instead of the real\n * Theme/Component types.\n */\ninterface ToolResultTextPartLike {\n\treadonly type: string;\n\treadonly text?: string;\n}\n\ninterface ToolResultLike {\n\treadonly content: readonly ToolResultTextPartLike[];\n}\n\ninterface RenderResultOptionsLike {\n\treadonly expanded: boolean;\n\treadonly isPartial: boolean;\n}\n\ninterface ThemeLike {\n\tfg(color: string, text: string): string;\n}\n\ninterface RenderContextLike {\n\treadonly lastComponent?: unknown;\n}\n\n/**\n * Simplified East Asian width table: these ranges cover the CJK blocks state\n * titles realistically use and count as 2 columns each; every other code\n * point counts as 1. Not a full wcwidth — combining marks and ambiguous\n * widths fall back to 1, the safe (narrower) default for row folding.\n */\nfunction isWideCodePoint(code: number): boolean {\n\treturn (\n\t\t(code >= 0x1100 && code <= 0x115f) ||\n\t\t(code >= 0x2e80 && code <= 0xa4cf) ||\n\t\t(code >= 0xac00 && code <= 0xd7a3) ||\n\t\t(code >= 0xf900 && code <= 0xfaff) ||\n\t\t(code >= 0xfe30 && code <= 0xfe4f) ||\n\t\t(code >= 0xff00 && code <= 0xff60) ||\n\t\t(code >= 0xffe0 && code <= 0xffe6) ||\n\t\t(code >= 0x20000 && code <= 0x3fffd)\n\t);\n}\n\n/** Folds one logical line into rows of at most `width` display columns. */\nfunction wrapLine(line: string, width: number): string[] {\n\tif (width <= 0) return [line];\n\tconst rows: string[] = [];\n\tlet row = \"\";\n\tlet columns = 0;\n\tfor (const character of line) {\n\t\tconst cost = isWideCodePoint(character.codePointAt(0) ?? 0) ? 2 : 1;\n\t\tif (row !== \"\" && columns + cost > width) {\n\t\t\trows.push(row);\n\t\t\trow = character;\n\t\t\tcolumns = cost;\n\t\t} else {\n\t\t\trow += character;\n\t\t\tcolumns += cost;\n\t\t}\n\t}\n\trows.push(row);\n\treturn rows;\n}\n\n/**\n * Structural TUI component rendering a goal_set result as the plain state\n * listing: `warning: ` lines take the host theme's warning color, every other\n * row toolOutput (color is decided per logical line, so a wrapped warning\n * stays warning-colored). Rows are cached per width — the host may render the\n * same result repeatedly at one width — and dropped on text updates or\n * invalidate.\n */\nclass GoalResultComponent {\n\tprivate text: string;\n\tprivate readonly theme: ThemeLike;\n\tprivate readonly rowsByWidth = new Map<number, string[]>();\n\n\tconstructor(text: string, theme: ThemeLike) {\n\t\tthis.text = text;\n\t\tthis.theme = theme;\n\t}\n\n\tsetText(text: string): void {\n\t\tif (text === this.text) return;\n\t\tthis.text = text;\n\t\tthis.rowsByWidth.clear();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst cached = this.rowsByWidth.get(width);\n\t\tif (cached) return cached;\n\t\tconst rows: string[] = [];\n\t\tfor (const line of this.text.split(\"\\n\")) {\n\t\t\tconst color = line.startsWith(\"warning: \") ? \"warning\" : \"toolOutput\";\n\t\t\tfor (const row of wrapLine(line, width)) rows.push(this.theme.fg(color, row));\n\t\t}\n\t\tthis.rowsByWidth.set(width, rows);\n\t\treturn rows;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.rowsByWidth.clear();\n\t}\n}\n\n/**\n * Parses the text part the host serialized from this plugin's execute return\n * (capability-runtime-guards normalization): a `GoalState`. Anything else —\n * a JSON.parse failure or a different shape — returns undefined so the\n * renderer falls back to the raw text.\n */\nfunction parseGoalResultText(text: string): { state: GoalState; warning?: string } | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n\tconst record = parsed as { goals?: unknown; warning?: unknown };\n\tif (!Array.isArray(record.goals)) return undefined;\n\tif (record.warning !== undefined && typeof record.warning !== \"string\") return undefined;\n\tconst state: GoalState = { goals: record.goals as GoalRecord[] };\n\treturn record.warning === undefined ? { state } : { state, warning: record.warning };\n}\n\n/**\n * renderResult for goal_set: re-renders the tool's own state payload as the\n * readable listing instead of the host's generic one-line JSON fallback.\n * Collapsed blocks show the summary and record lines only; expanded blocks\n * add the evidence lines; a `warning` field, if a response ever carries one,\n * rides along as a final warning-colored line. Non-state payloads and\n * malformed records render verbatim as toolOutput.\n */\nfunction renderGoalResult(\n\tresult: ToolResultLike,\n\toptions: RenderResultOptionsLike,\n\ttheme: ThemeLike,\n\tcontext: RenderContextLike,\n): GoalResultComponent | undefined {\n\tconst text = result.content.find((part) => part.type === \"text\")?.text;\n\tif (text === undefined || text === \"\") return undefined;\n\tconst parsed = parseGoalResultText(text);\n\tlet listing: string;\n\tif (parsed) {\n\t\t// The parse gate is envelope-level, so a parsed state may still hold\n\t\t// malformed records (null elements, a record missing evidence).\n\t\t// Rendering those must not throw out of the plugin: fall back to the\n\t\t// raw text, the same path as a parse failure.\n\t\ttry {\n\t\t\tlisting = renderGoalState(parsed.state, { evidence: options.expanded });\n\t\t\tif (parsed.warning !== undefined) {\n\t\t\t\t// Fold newlines like the record fields so the warning stays one line.\n\t\t\t\tlisting = `${listing}\\nwarning: ${parsed.warning.replace(/\\n/g, \" \")}`;\n\t\t\t}\n\t\t} catch {\n\t\t\tlisting = text;\n\t\t}\n\t} else {\n\t\tlisting = text;\n\t}\n\t// Reuse the host-provided component for this render slot (the ls.ts\n\t// precedent): same instance in, same instance out, with fresh text.\n\tif (context.lastComponent instanceof GoalResultComponent) {\n\t\tcontext.lastComponent.setText(listing);\n\t\treturn context.lastComponent;\n\t}\n\treturn new GoalResultComponent(listing, theme);\n}\n\nexport function createCapability(\n\tapi: CapabilityAPI,\n): Promise<readonly DisposableRegistration[]> | readonly DisposableRegistration[] {\n\tlet state = loadState(api);\n\tconst refreshState = (): GoalState => {\n\t\tif (api.session) state = loadState(api);\n\t\treturn state;\n\t};\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\trenderResult: renderGoalResult,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\trefreshState();\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\tconst command = api.registerCommand({\n\t\tname: \"goal\",\n\t\tdescription: \"Show this session's goals (status and evidence)\",\n\t\texecute: async (args: string) => {\n\t\t\ttry {\n\t\t\t\tif (args.trim() !== \"\") return { status: \"error\" as const, message: \"Usage: /goal\" };\n\t\t\t\treturn { status: \"success\" as const, message: renderGoalState(refreshState()) };\n\t\t\t} catch (error) {\n\t\t\t\t// A slash command must not crash the host (memory /promote precedent).\n\t\t\t\treturn { status: \"error\" as const, message: error instanceof Error ? error.message : String(error) };\n\t\t\t}\n\t\t},\n\t});\n\n\treturn [tool, command];\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-agent-forge/plugin-goal",
3
- "version": "0.88.15",
3
+ "version": "0.88.17",
4
4
  "description": "Goal capability plugin for Agent Forge (goal_set state machine over session entries)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "typebox": "1.3.27",
48
- "@ai-agent-forge/plugin-sdk": "^0.88.15"
48
+ "@ai-agent-forge/plugin-sdk": "^0.88.17"
49
49
  },
50
50
  "devDependencies": {
51
51
  "shx": "0.3.4",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "agent-forge.plugin.goal",
3
- "version": "0.88.15",
3
+ "version": "0.88.17",
4
4
  "apiVersion": "1-draft",
5
5
  "entry": "./dist/entry.js",
6
6
  "hostVersion": ">=0.84.4",