chainlesschain 0.45.81 → 0.47.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.
Files changed (71) hide show
  1. package/README.md +10 -0
  2. package/bin/chainlesschain.js +0 -0
  3. package/package.json +1 -1
  4. package/src/assets/web-panel/.build-hash +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-C1AnPdMx.js → Analytics-DgypYeUB.js} +2 -2
  6. package/src/assets/web-panel/assets/AppLayout-Bzf3mSZI.js +1 -0
  7. package/src/assets/web-panel/assets/AppLayout-DQyDwGut.css +1 -0
  8. package/src/assets/web-panel/assets/{Backup-D31iZX3l.js → Backup-Ba9UybpT.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DiXJ3TuK.js → Chat-BwXskT21.js} +1 -1
  10. package/src/assets/web-panel/assets/Cowork-CXuhlHew.css +1 -0
  11. package/src/assets/web-panel/assets/Cowork-UmOe7qvE.js +7 -0
  12. package/src/assets/web-panel/assets/{Cron-DBt1ueXh.js → Cron-JHS-rc-4.js} +2 -2
  13. package/src/assets/web-panel/assets/{Dashboard-HPh9FcPt.js → Dashboard-B95cMCO7.js} +2 -2
  14. package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +1 -0
  15. package/src/assets/web-panel/assets/{Git-hwQ1oZHj.js → Git-CSYO0_zk.js} +2 -2
  16. package/src/assets/web-panel/assets/{Logs-4D9p6PRM.js → Logs-Hxw_K0km.js} +2 -2
  17. package/src/assets/web-panel/assets/{McpTools-CyAUjbbs.js → McpTools-DIE75TrB.js} +2 -2
  18. package/src/assets/web-panel/assets/{Memory-BMqOR7S-.js → Memory-C4KVnLlp.js} +2 -2
  19. package/src/assets/web-panel/assets/{Notes-Cmas8i4E.js → Notes-DuzrHMAk.js} +2 -2
  20. package/src/assets/web-panel/assets/{Organization-DnSa58Tl.js → Organization-DTq6uF82.js} +4 -4
  21. package/src/assets/web-panel/assets/{P2P-BxksIBWs.js → P2P-C0hjlhsR.js} +2 -2
  22. package/src/assets/web-panel/assets/{Permissions-Bq5Qn2s3.js → Permissions-Ec0NH-xC.js} +4 -4
  23. package/src/assets/web-panel/assets/{Projects-B7EM0uPg.js → Projects-U8D0asCS.js} +2 -2
  24. package/src/assets/web-panel/assets/{Providers-DAwgG5KV.js → Providers-BngtTLvJ.js} +2 -2
  25. package/src/assets/web-panel/assets/{RssFeed-HSZoRXvS.js → RssFeed-B9NbwCKM.js} +3 -3
  26. package/src/assets/web-panel/assets/{Security-Cz17qBny.js → Security-BL5Rkr1T.js} +3 -3
  27. package/src/assets/web-panel/assets/{Services-D2EsLq-v.js → Services-D4MJzLld.js} +2 -2
  28. package/src/assets/web-panel/assets/{Skills-C9v-f3vZ.js → Skills-CQTOMDwF.js} +1 -1
  29. package/src/assets/web-panel/assets/{Tasks-yMEcU0n7.js → Tasks-DepbJMnL.js} +1 -1
  30. package/src/assets/web-panel/assets/{Templates-l7SvlKuB.js → Templates-C24PVZPu.js} +1 -1
  31. package/src/assets/web-panel/assets/{Wallet-BHWhLWn9.js → Wallet-PQoSpN_P.js} +3 -3
  32. package/src/assets/web-panel/assets/{WebAuthn-kWhFYaUK.js → WebAuthn-BcuyQ4Lr.js} +4 -4
  33. package/src/assets/web-panel/assets/WorkflowEditor-C-SvXbHW.js +1 -0
  34. package/src/assets/web-panel/assets/WorkflowEditor-D5bX6woe.css +1 -0
  35. package/src/assets/web-panel/assets/{antd-D6h4fDFf.js → antd-DEjZPGMj.js} +82 -82
  36. package/src/assets/web-panel/assets/index-CwvzTTw_.js +2 -0
  37. package/src/assets/web-panel/assets/{markdown-BZsB-Dsv.js → markdown-CusdXFxb.js} +1 -1
  38. package/src/assets/web-panel/index.html +2 -2
  39. package/src/commands/cowork.js +867 -0
  40. package/src/gateways/ws/action-protocol.js +182 -2
  41. package/src/gateways/ws/message-dispatcher.js +5 -0
  42. package/src/gateways/ws/ws-server.js +21 -0
  43. package/src/lib/cowork-cron.js +474 -0
  44. package/src/lib/cowork-evomap-adapter.js +121 -0
  45. package/src/lib/cowork-learning.js +438 -0
  46. package/src/lib/cowork-mcp-tools.js +182 -0
  47. package/src/lib/cowork-observe-html.js +108 -0
  48. package/src/lib/cowork-observe.js +160 -0
  49. package/src/lib/cowork-share.js +322 -0
  50. package/src/lib/cowork-task-runner.js +317 -3
  51. package/src/lib/cowork-task-templates.js +101 -13
  52. package/src/lib/cowork-template-marketplace.js +205 -0
  53. package/src/lib/cowork-workflow.js +571 -0
  54. package/src/lib/provider-options.js +133 -0
  55. package/src/lib/skill-loader.js +65 -0
  56. package/src/lib/sub-agent-context.js +54 -2
  57. package/src/lib/sub-agent-profiles.js +164 -0
  58. package/src/lib/todo-manager.js +108 -0
  59. package/src/lib/turn-context.js +95 -0
  60. package/src/lib/web-fetch.js +224 -0
  61. package/src/lib/workflow-expr.js +318 -0
  62. package/src/repl/agent-repl.js +4 -0
  63. package/src/runtime/agent-core.js +135 -3
  64. package/src/runtime/coding-agent-contract-shared.cjs +131 -0
  65. package/src/runtime/coding-agent-policy.cjs +30 -0
  66. package/src/assets/web-panel/assets/AppLayout-YdvJBMHH.js +0 -1
  67. package/src/assets/web-panel/assets/AppLayout-cxfKLu-m.css +0 -1
  68. package/src/assets/web-panel/assets/Cowork-BnrHWwZw.js +0 -7
  69. package/src/assets/web-panel/assets/Cowork-CcSoS3eX.css +0 -1
  70. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +0 -1
  71. package/src/assets/web-panel/assets/index-ByUk2Wmr.js +0 -2
@@ -109,6 +109,71 @@ export function parseSkillMd(content) {
109
109
  return { data, body };
110
110
  }
111
111
 
112
+ /**
113
+ * Substitute $ARGUMENTS / $1 / $2 / ... placeholders in a skill body.
114
+ * Inspired by open-agents substituteArguments.
115
+ *
116
+ * Rules:
117
+ * - $ARGUMENTS → full args string (joined by space if array)
118
+ * - $1, $2, ... → positional args (shell-like; split on whitespace if string)
119
+ * - Escape $ via $$ → literal $
120
+ * - Unmatched placeholders are left as-is (non-destructive)
121
+ *
122
+ * @param {string} body - Skill body text
123
+ * @param {string|string[]} args - Args as raw string or pre-split array
124
+ * @returns {string}
125
+ */
126
+ export function substituteArguments(body, args) {
127
+ if (typeof body !== "string" || body.length === 0) return body || "";
128
+ let full = "";
129
+ let positional = [];
130
+ if (Array.isArray(args)) {
131
+ positional = args.map((a) => String(a));
132
+ full = positional.join(" ");
133
+ } else if (typeof args === "string") {
134
+ full = args;
135
+ positional = args.trim() === "" ? [] : args.trim().split(/\s+/);
136
+ }
137
+ // Protect literal $$
138
+ const MARKER = "\u0000DOLLAR\u0000";
139
+ let out = body.replace(/\$\$/g, MARKER);
140
+ out = out.replace(/\$ARGUMENTS\b/g, full);
141
+ out = out.replace(/\$(\d+)/g, (match, idx) => {
142
+ const i = parseInt(idx, 10) - 1;
143
+ if (i < 0 || i >= positional.length) return match;
144
+ return positional[i];
145
+ });
146
+ return out.replace(new RegExp(MARKER, "g"), "$");
147
+ }
148
+
149
+ /**
150
+ * Prepend `Skill directory: <abs>` line to body so the LLM can resolve
151
+ * relative paths declared inside the SKILL.md.
152
+ * Inspired by open-agents injectSkillDirectory.
153
+ *
154
+ * @param {string} body
155
+ * @param {string} skillDir - Absolute path to the skill directory
156
+ * @returns {string}
157
+ */
158
+ export function injectSkillDirectory(body, skillDir) {
159
+ if (!skillDir) return body || "";
160
+ const header = `Skill directory: ${skillDir}\n\n`;
161
+ return header + (body || "");
162
+ }
163
+
164
+ /**
165
+ * Prepare a skill body for execution: substitute $ARGUMENTS / $N placeholders
166
+ * and prepend the skill directory header.
167
+ *
168
+ * @param {object} skill - Skill metadata (must have .body and .skillDir)
169
+ * @param {string|string[]} args - Runtime args
170
+ * @returns {string}
171
+ */
172
+ export function prepareSkillBody(skill, args) {
173
+ const withArgs = substituteArguments(skill?.body || "", args);
174
+ return injectSkillDirectory(withArgs, skill?.skillDir);
175
+ }
176
+
112
177
  /**
113
178
  * Multi-layer CLI skill loader
114
179
  */
@@ -61,7 +61,14 @@ export class SubAgentContext {
61
61
  this.parentId = options.parentId || null;
62
62
  this.role = options.role || "general";
63
63
  this.task = options.task || "";
64
- this.maxIterations = options.maxIterations || DEFAULT_MAX_ITERATIONS;
64
+ // Declarative profile (Phase 3) — explorer/executor/design, etc.
65
+ // Provides systemPrompt + maxIterations + modelHint defaults that
66
+ // explicit options can still override.
67
+ this._profile = options.profile || null;
68
+ this.maxIterations =
69
+ options.maxIterations ||
70
+ this._profile?.maxIterations ||
71
+ DEFAULT_MAX_ITERATIONS;
65
72
  this.iterationBudget = options.iterationBudget || null; // shared budget from parent
66
73
  this.tokenBudget = options.tokenBudget || null;
67
74
  this.inheritedContext = options.inheritedContext || null;
@@ -107,8 +114,30 @@ export class SubAgentContext {
107
114
  // Optional abort signal for cancellation
108
115
  this._signal = options.signal || null;
109
116
 
117
+ // Optional MCP / external tool plumbing. These are forwarded into the
118
+ // agentLoop options so MCP-backed tools (e.g. from a cowork template's
119
+ // `mcpServers`) appear in the LLM's tool list and route through
120
+ // `mcpClient.callTool()` in agent-core's default-case dispatch.
121
+ this._extraToolDefinitions = Array.isArray(options.extraToolDefinitions)
122
+ ? options.extraToolDefinitions
123
+ : [];
124
+ this._externalToolDescriptors =
125
+ options.externalToolDescriptors &&
126
+ typeof options.externalToolDescriptors === "object"
127
+ ? options.externalToolDescriptors
128
+ : {};
129
+ this._externalToolExecutors =
130
+ options.externalToolExecutors &&
131
+ typeof options.externalToolExecutors === "object"
132
+ ? options.externalToolExecutors
133
+ : {};
134
+ this._mcpClient = options.mcpClient || null;
135
+
110
136
  // Build isolated system prompt
111
137
  const basePrompt = buildSystemPrompt(this.cwd);
138
+ const profilePrompt = this._profile?.systemPrompt
139
+ ? `\n\n## Profile: ${this._profile.name}\n${this._profile.systemPrompt}`
140
+ : "";
112
141
  const rolePrompt = `\n\n## Sub-Agent Role: ${this.role}\nYou are a focused sub-agent with the role "${this.role}". Your task is:\n${this.task}\n\nStay focused on this specific task. Be concise and return results directly.`;
113
142
  const contextSection = this.inheritedContext
114
143
  ? `\n\n## Parent Context\n${this.inheritedContext}`
@@ -116,7 +145,7 @@ export class SubAgentContext {
116
145
 
117
146
  this.messages.push({
118
147
  role: "system",
119
- content: basePrompt + rolePrompt + contextSection,
148
+ content: basePrompt + profilePrompt + rolePrompt + contextSection,
120
149
  });
121
150
  }
122
151
 
@@ -226,6 +255,29 @@ export class SubAgentContext {
226
255
  options.iterationBudget = this.iterationBudget;
227
256
  }
228
257
 
258
+ // Forward MCP / external tool plumbing into the agent loop
259
+ if (this._extraToolDefinitions.length > 0) {
260
+ options.extraToolDefinitions = [
261
+ ...(options.extraToolDefinitions || []),
262
+ ...this._extraToolDefinitions,
263
+ ];
264
+ }
265
+ if (Object.keys(this._externalToolDescriptors).length > 0) {
266
+ options.externalToolDescriptors = {
267
+ ...(options.externalToolDescriptors || {}),
268
+ ...this._externalToolDescriptors,
269
+ };
270
+ }
271
+ if (Object.keys(this._externalToolExecutors).length > 0) {
272
+ options.externalToolExecutors = {
273
+ ...(options.externalToolExecutors || {}),
274
+ ...this._externalToolExecutors,
275
+ };
276
+ }
277
+ if (this._mcpClient) {
278
+ options.mcpClient = this._mcpClient;
279
+ }
280
+
229
281
  try {
230
282
  // Use a separate messages array for the agent loop
231
283
  // The agentLoop will append to this.messages directly
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Sub-Agent Profiles — declarative registry of subagent roles.
3
+ *
4
+ * Inspired by open-agents SUBAGENT_REGISTRY. Separate from the runtime
5
+ * `sub-agent-registry.js` which tracks *instances*; this module describes
6
+ * the *kinds* (explorer/executor/design) a parent agent may delegate to.
7
+ *
8
+ * Each profile defines:
9
+ * - name stable identifier used by spawn_sub_agent
10
+ * - shortDescription one-line hook for the parent prompt
11
+ * - systemPrompt prepended to sub-agent messages[0]
12
+ * - toolAllowlist array of tool names the sub-agent may call
13
+ * (null = inherit all)
14
+ * - maxIterations optional per-profile iteration cap
15
+ * - modelHint optional { category } hint for llm-manager
16
+ */
17
+
18
+ const READONLY_TOOLS = Object.freeze([
19
+ "read_file",
20
+ "list_dir",
21
+ "search_files",
22
+ "search_sessions",
23
+ "web_fetch",
24
+ "list_skills",
25
+ ]);
26
+
27
+ const FULL_TOOLS = Object.freeze([
28
+ "read_file",
29
+ "write_file",
30
+ "edit_file",
31
+ "edit_file_hashed",
32
+ "list_dir",
33
+ "search_files",
34
+ "search_sessions",
35
+ "run_shell",
36
+ "git",
37
+ "run_code",
38
+ "run_skill",
39
+ "list_skills",
40
+ "web_fetch",
41
+ "todo_write",
42
+ "ask_user_question",
43
+ ]);
44
+
45
+ const DESIGN_TOOLS = Object.freeze([
46
+ "read_file",
47
+ "write_file",
48
+ "edit_file",
49
+ "edit_file_hashed",
50
+ "list_dir",
51
+ "search_files",
52
+ "web_fetch",
53
+ "run_skill",
54
+ "list_skills",
55
+ "todo_write",
56
+ ]);
57
+
58
+ const _builtinProfiles = {
59
+ explorer: {
60
+ name: "explorer",
61
+ shortDescription:
62
+ "Read-only researcher. Investigates code, searches files/sessions, fetches web docs. Cannot write or execute.",
63
+ systemPrompt:
64
+ "You are a read-only research sub-agent. Your job is to gather facts and report back concisely. You MUST NOT write files or execute commands. When done, return a structured summary of findings.",
65
+ toolAllowlist: READONLY_TOOLS,
66
+ maxIterations: 20,
67
+ modelHint: { category: "quick" },
68
+ },
69
+ executor: {
70
+ name: "executor",
71
+ shortDescription:
72
+ "Full-permission implementer. Writes code, runs tests, executes shell/git. Use for end-to-end task completion.",
73
+ systemPrompt:
74
+ "You are a full-permission execution sub-agent. Implement the task to completion. Prefer edit_file_hashed over edit_file. Always verify with tests/build when relevant. Return a summary plus list of files changed.",
75
+ toolAllowlist: FULL_TOOLS,
76
+ maxIterations: 40,
77
+ modelHint: { category: "deep" },
78
+ },
79
+ design: {
80
+ name: "design",
81
+ shortDescription:
82
+ "Frontend/UI specialist. Produces polished Vue/React/HTML with distinctive aesthetics. No shell/git access.",
83
+ systemPrompt:
84
+ "You are a frontend design sub-agent. Produce high-quality, production-grade UI code. Avoid generic AI aesthetics. Prefer semantic HTML, accessible components, and thoughtful typography. You may read/write files and fetch references from the web, but cannot run shell or git.",
85
+ toolAllowlist: DESIGN_TOOLS,
86
+ maxIterations: 30,
87
+ modelHint: { category: "creative" },
88
+ },
89
+ };
90
+
91
+ const _registry = new Map(Object.entries(_builtinProfiles));
92
+
93
+ export function getSubAgentProfile(name) {
94
+ if (!name) return null;
95
+ const entry = _registry.get(name);
96
+ if (!entry) return null;
97
+ return {
98
+ ...entry,
99
+ toolAllowlist: Array.isArray(entry.toolAllowlist)
100
+ ? [...entry.toolAllowlist]
101
+ : null,
102
+ };
103
+ }
104
+
105
+ export function listSubAgentProfiles() {
106
+ return Array.from(_registry.values()).map((p) => ({
107
+ ...p,
108
+ toolAllowlist: Array.isArray(p.toolAllowlist) ? [...p.toolAllowlist] : null,
109
+ }));
110
+ }
111
+
112
+ /**
113
+ * Register a custom profile (or override a built-in).
114
+ * Returns true on success, false on invalid input.
115
+ */
116
+ export function registerSubAgentProfile(profile) {
117
+ if (!profile || typeof profile.name !== "string" || !profile.name) {
118
+ return false;
119
+ }
120
+ if (typeof profile.shortDescription !== "string") return false;
121
+ if (typeof profile.systemPrompt !== "string") return false;
122
+ const toolAllowlist = Array.isArray(profile.toolAllowlist)
123
+ ? [...profile.toolAllowlist]
124
+ : null;
125
+ _registry.set(profile.name, {
126
+ name: profile.name,
127
+ shortDescription: profile.shortDescription,
128
+ systemPrompt: profile.systemPrompt,
129
+ toolAllowlist,
130
+ maxIterations:
131
+ typeof profile.maxIterations === "number" ? profile.maxIterations : 20,
132
+ modelHint: profile.modelHint || null,
133
+ });
134
+ return true;
135
+ }
136
+
137
+ export function unregisterSubAgentProfile(name) {
138
+ return _registry.delete(name);
139
+ }
140
+
141
+ export function resetToBuiltins() {
142
+ _registry.clear();
143
+ for (const [k, v] of Object.entries(_builtinProfiles)) {
144
+ _registry.set(k, v);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Build a one-section system-prompt snippet listing available subagents.
150
+ * Inspired by open-agents buildSubagentSummaryLines.
151
+ *
152
+ * @returns {string}
153
+ */
154
+ export function buildSubagentSummaryLines() {
155
+ const profiles = listSubAgentProfiles();
156
+ if (profiles.length === 0) return "";
157
+ const lines = ["## Available sub-agents (via spawn_sub_agent)"];
158
+ for (const p of profiles) {
159
+ lines.push(`- **${p.name}**: ${p.shortDescription}`);
160
+ }
161
+ return lines.join("\n");
162
+ }
163
+
164
+ export const _deps = { _registry, _builtinProfiles };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Session TODO Manager
3
+ *
4
+ * In-memory per-session TODO list. One instance per sessionId.
5
+ * Inspired by open-agents todo_write tool.
6
+ *
7
+ * Contract:
8
+ * - Exactly one item may be in_progress at a time (validator enforces)
9
+ * - writeTodos replaces the full list (idempotent updates)
10
+ * - getTodos returns a deep-cloned array
11
+ */
12
+
13
+ const VALID_STATUSES = Object.freeze([
14
+ "pending",
15
+ "in_progress",
16
+ "completed",
17
+ "cancelled",
18
+ ]);
19
+
20
+ const _stores = new Map();
21
+
22
+ export function getTodoStore(sessionId) {
23
+ const key = sessionId || "__default__";
24
+ if (!_stores.has(key)) {
25
+ _stores.set(key, { todos: [] });
26
+ }
27
+ return _stores.get(key);
28
+ }
29
+
30
+ export function validateTodos(todos) {
31
+ if (!Array.isArray(todos)) {
32
+ return { valid: false, error: "todos must be an array" };
33
+ }
34
+ const ids = new Set();
35
+ let inProgressCount = 0;
36
+ for (const todo of todos) {
37
+ if (!todo || typeof todo !== "object") {
38
+ return { valid: false, error: "each todo must be an object" };
39
+ }
40
+ if (typeof todo.id !== "string" || !todo.id) {
41
+ return { valid: false, error: "todo.id must be a non-empty string" };
42
+ }
43
+ if (ids.has(todo.id)) {
44
+ return { valid: false, error: `duplicate todo id: ${todo.id}` };
45
+ }
46
+ ids.add(todo.id);
47
+ if (typeof todo.content !== "string" || !todo.content) {
48
+ return { valid: false, error: `todo.content required for id=${todo.id}` };
49
+ }
50
+ if (!VALID_STATUSES.includes(todo.status)) {
51
+ return {
52
+ valid: false,
53
+ error: `todo.status must be one of ${VALID_STATUSES.join("|")} (id=${todo.id})`,
54
+ };
55
+ }
56
+ if (todo.status === "in_progress") inProgressCount += 1;
57
+ }
58
+ if (inProgressCount > 1) {
59
+ return {
60
+ valid: false,
61
+ error: "only one todo may be in_progress at a time",
62
+ };
63
+ }
64
+ return { valid: true };
65
+ }
66
+
67
+ export function writeTodos(sessionId, todos) {
68
+ const check = validateTodos(todos);
69
+ if (!check.valid) {
70
+ return { success: false, error: check.error };
71
+ }
72
+ const store = getTodoStore(sessionId);
73
+ store.todos = todos.map((t) => ({
74
+ id: t.id,
75
+ content: t.content,
76
+ status: t.status,
77
+ }));
78
+ return {
79
+ success: true,
80
+ count: store.todos.length,
81
+ summary: summarizeTodos(store.todos),
82
+ };
83
+ }
84
+
85
+ export function getTodos(sessionId) {
86
+ const store = getTodoStore(sessionId);
87
+ return store.todos.map((t) => ({ ...t }));
88
+ }
89
+
90
+ export function clearTodos(sessionId) {
91
+ const store = getTodoStore(sessionId);
92
+ store.todos = [];
93
+ return { success: true };
94
+ }
95
+
96
+ export function summarizeTodos(todos) {
97
+ const counts = { pending: 0, in_progress: 0, completed: 0, cancelled: 0 };
98
+ for (const t of todos || []) {
99
+ if (counts[t.status] !== undefined) counts[t.status] += 1;
100
+ }
101
+ return counts;
102
+ }
103
+
104
+ export function resetAllStores() {
105
+ _stores.clear();
106
+ }
107
+
108
+ export const _deps = { _stores };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Turn-scoped context builder — inspired by open-agents' prepareCall.
3
+ *
4
+ * Produces a short system-prompt supplement that is re-computed before each
5
+ * LLM call in the agent loop. Gives the model fresh runtime signals (cwd,
6
+ * git HEAD/branch/dirty, active skills, turn counter) without polluting the
7
+ * persistent message history.
8
+ *
9
+ * Callers: agent-core.agentLoop's pre-call hook, via options.prepareCall.
10
+ *
11
+ * @module turn-context
12
+ */
13
+
14
+ import { execSync } from "child_process";
15
+ import path from "path";
16
+
17
+ const _deps = { execSync };
18
+
19
+ /**
20
+ * Run a git command with stdio pipe and return stdout or null.
21
+ * @param {string} cmd
22
+ * @param {string} cwd
23
+ * @returns {string|null}
24
+ */
25
+ function _git(cmd, cwd) {
26
+ try {
27
+ return _deps
28
+ .execSync(`git ${cmd}`, {
29
+ cwd,
30
+ encoding: "utf-8",
31
+ stdio: ["ignore", "pipe", "ignore"],
32
+ timeout: 1500,
33
+ })
34
+ .trim();
35
+ } catch (_e) {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Build a compact turn-scoped context block.
42
+ *
43
+ * @param {object} input
44
+ * @param {number} [input.iteration] - 1-based iteration counter for this turn.
45
+ * @param {string} [input.cwd] - Working directory (defaults to process.cwd()).
46
+ * @param {string|null} [input.sessionId] - Agent session id.
47
+ * @param {string[]} [input.activeSkills] - Names of skills currently active.
48
+ * @returns {string} Markdown-formatted supplement, or empty string if nothing useful.
49
+ */
50
+ export function buildTurnContext({
51
+ iteration = 1,
52
+ cwd = process.cwd(),
53
+ sessionId = null,
54
+ activeSkills = [],
55
+ } = {}) {
56
+ const lines = [];
57
+ lines.push(`## Turn context (iteration ${iteration})`);
58
+ lines.push(`- cwd: ${path.resolve(cwd)}`);
59
+
60
+ const branch = _git("rev-parse --abbrev-ref HEAD", cwd);
61
+ if (branch) {
62
+ const head = _git("rev-parse --short HEAD", cwd);
63
+ const status = _git("status --porcelain", cwd);
64
+ const dirty = status && status.length > 0;
65
+ const fileCount = dirty ? status.split("\n").filter(Boolean).length : 0;
66
+ lines.push(
67
+ `- git: ${branch}@${head || "?"}${dirty ? ` (${fileCount} uncommitted)` : " (clean)"}`,
68
+ );
69
+ }
70
+
71
+ if (sessionId) {
72
+ lines.push(`- session: ${sessionId}`);
73
+ }
74
+
75
+ if (Array.isArray(activeSkills) && activeSkills.length > 0) {
76
+ lines.push(`- active skills: ${activeSkills.join(", ")}`);
77
+ }
78
+
79
+ return lines.join("\n");
80
+ }
81
+
82
+ /**
83
+ * Default prepareCall implementation — builds a turn-context supplement and
84
+ * returns it as a structured payload. agent-core wraps this into a transient
85
+ * system message for the next llmCall without mutating persistent history.
86
+ *
87
+ * @param {object} ctx - Supplied by agent-core at call site.
88
+ * @returns {{ systemSuffix: string } | null}
89
+ */
90
+ export function defaultPrepareCall(ctx) {
91
+ const supplement = buildTurnContext(ctx);
92
+ return supplement ? { systemSuffix: supplement } : null;
93
+ }
94
+
95
+ export { _deps };