@xn-intenton-z2a/agentic-lib 7.4.8 → 7.4.9

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 (45) hide show
  1. package/{src → .github}/agents/agent-apply-fix.md +10 -0
  2. package/{src → .github}/agents/agent-director.md +10 -0
  3. package/{src → .github}/agents/agent-discovery.md +8 -0
  4. package/{src → .github}/agents/agent-discussion-bot.md +9 -0
  5. package/{src → .github}/agents/agent-issue-resolution.md +12 -0
  6. package/{src → .github}/agents/agent-iterate.md +8 -0
  7. package/{src → .github}/agents/agent-maintain-features.md +8 -0
  8. package/{src → .github}/agents/agent-maintain-library.md +7 -0
  9. package/{src → .github}/agents/agent-review-issue.md +8 -0
  10. package/{src → .github}/agents/agent-supervisor.md +9 -0
  11. package/.github/workflows/agentic-lib-test.yml +4 -2
  12. package/.github/workflows/agentic-lib-workflow.yml +70 -26
  13. package/README.md +5 -7
  14. package/agentic-lib.toml +16 -38
  15. package/bin/agentic-lib.js +49 -60
  16. package/package.json +3 -4
  17. package/src/actions/agentic-step/action.yml +1 -1
  18. package/src/actions/agentic-step/copilot.js +0 -5
  19. package/src/actions/agentic-step/index.js +8 -1
  20. package/src/actions/agentic-step/logging.js +14 -2
  21. package/src/actions/agentic-step/tasks/direct.js +86 -65
  22. package/src/actions/agentic-step/tasks/discussions.js +198 -264
  23. package/src/actions/agentic-step/tasks/enhance-issue.js +84 -33
  24. package/src/actions/agentic-step/tasks/fix-code.js +111 -57
  25. package/src/actions/agentic-step/tasks/maintain-features.js +69 -52
  26. package/src/actions/agentic-step/tasks/maintain-library.js +57 -19
  27. package/src/actions/agentic-step/tasks/resolve-issue.js +43 -18
  28. package/src/actions/agentic-step/tasks/review-issue.js +117 -117
  29. package/src/actions/agentic-step/tasks/supervise.js +140 -151
  30. package/src/actions/agentic-step/tasks/transform.js +106 -258
  31. package/src/copilot/agents.js +2 -2
  32. package/src/copilot/config.js +2 -18
  33. package/src/copilot/{hybrid-session.js → copilot-session.js} +39 -7
  34. package/src/copilot/github-tools.js +514 -0
  35. package/src/copilot/guards.js +1 -1
  36. package/src/copilot/session.js +0 -141
  37. package/src/copilot/tools.js +4 -0
  38. package/src/iterate.js +1 -1
  39. package/src/scripts/push-to-logs.sh +1 -1
  40. package/src/seeds/zero-SCREENSHOT_INDEX.png +0 -0
  41. package/src/seeds/zero-package.json +1 -1
  42. package/src/agents/agentic-lib.yml +0 -66
  43. package/src/copilot/context.js +0 -457
  44. package/src/mcp/server.js +0 -830
  45. /package/{src → .github}/agents/agent-ready-issue.md +0 -0
@@ -2,15 +2,20 @@
2
2
  // Copyright (C) 2025-2026 Polycode Limited
3
3
  // tasks/discussions.js — GitHub Discussions bot
4
4
  //
5
- // Responds to GitHub Discussions, creates features, seeds repositories,
6
- // and provides status updates. Uses the Copilot SDK for natural conversation.
5
+ // Uses runCopilotSession with discussion tools: the model fetches, searches,
6
+ // and posts to discussions via tools instead of front-loaded prompts.
7
7
 
8
8
  import * as core from "@actions/core";
9
- import { existsSync, writeFileSync } from "fs";
10
- import { runCopilotTask, readOptionalFile, scanDirectory } from "../copilot.js";
9
+ import { writeFileSync } from "fs";
10
+ import { readOptionalFile, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
11
+ import { runCopilotSession } from "../../../copilot/copilot-session.js";
12
+ import { createDiscussionTools, createGitHubTools } from "../../../copilot/github-tools.js";
11
13
 
12
14
  const BOT_LOGINS = ["github-actions[bot]", "github-actions"];
13
15
 
16
+ /**
17
+ * Pre-fetch discussion for the lean prompt (gives the model initial context).
18
+ */
14
19
  async function fetchDiscussion(octokit, discussionUrl, commentsLimit = 10) {
15
20
  const urlMatch = discussionUrl.match(/github\.com\/([^/]+)\/([^/]+)\/discussions\/(\d+)/);
16
21
  if (!urlMatch) {
@@ -54,38 +59,66 @@ async function fetchDiscussion(octokit, discussionUrl, commentsLimit = 10) {
54
59
  }
55
60
  }
56
61
 
57
- function buildPrompt(discussionUrl, discussion, context, t, repoContext, triggerComment) {
58
- const { config, instructions } = context;
59
- const { title, body, comments } = discussion;
62
+ /**
63
+ * Respond to a GitHub Discussion using the Copilot SDK with tool-driven exploration.
64
+ *
65
+ * @param {Object} context - Task context from index.js
66
+ * @returns {Promise<Object>} Result with outcome, action, tokensUsed, model
67
+ */
68
+ export async function discussions(context) {
69
+ const { octokit, model, discussionUrl, repo, config, logFilePath, screenshotFilePath } = context;
70
+ const t = config?.tuning || {};
71
+
72
+ if (!discussionUrl) {
73
+ throw new Error("discussions task requires discussion-url input");
74
+ }
75
+
76
+ // Pre-fetch discussion for the lean prompt
77
+ const discussion = await fetchDiscussion(octokit, discussionUrl, t.discussionComments || 10);
60
78
 
61
- const humanComments = comments.filter((c) => !BOT_LOGINS.includes(c.author?.login));
62
- const botReplies = comments.filter((c) => BOT_LOGINS.includes(c.author?.login));
79
+ // Filter discussion comments to only those after the most recent init
80
+ const initTs = config?.init?.timestamp || null;
81
+ if (initTs && discussion.comments.length > 0) {
82
+ const initDate = new Date(initTs);
83
+ discussion.comments = discussion.comments.filter((c) => new Date(c.createdAt) >= initDate);
84
+ }
85
+
86
+ const humanComments = discussion.comments.filter((c) => !BOT_LOGINS.includes(c.author?.login));
87
+ const botReplies = discussion.comments.filter((c) => BOT_LOGINS.includes(c.author?.login));
63
88
  const latestHumanComment = humanComments.length > 0 ? humanComments[humanComments.length - 1] : null;
64
89
  const lastBotReply = botReplies.length > 0 ? botReplies[botReplies.length - 1] : null;
65
90
 
91
+ // Extract trigger comment info
92
+ const triggerComment = {};
93
+ const eventComment = context.github?.payload?.comment;
94
+ if (eventComment) {
95
+ triggerComment.nodeId = eventComment.node_id || "";
96
+ triggerComment.createdAt = eventComment.created_at || "";
97
+ triggerComment.body = eventComment.body || "";
98
+ triggerComment.login = eventComment.user?.login || "";
99
+ core.info(`Trigger comment from event payload: ${triggerComment.login} at ${triggerComment.createdAt}`);
100
+ }
101
+ if (context.commentNodeId) triggerComment.nodeId = context.commentNodeId;
102
+ if (context.commentCreatedAt) triggerComment.createdAt = context.commentCreatedAt;
103
+
66
104
  const mission = readOptionalFile(config.paths.mission.path);
67
- const contributing = readOptionalFile(config.paths.contributing.path, t.documentSummary || 1000);
68
- const featuresPath = config.paths.features.path;
69
- const featureNames = existsSync(featuresPath)
70
- ? scanDirectory(featuresPath, ".md").map((f) => f.name.replace(".md", ""))
71
- : [];
72
- const recentActivity = readOptionalFile(config.intentionBot.intentionFilepath).split("\n").slice(-20).join("\n");
73
- const agentInstructions = instructions || "Respond to the GitHub Discussion as the repository bot.";
74
-
75
- const parts = [
105
+ const agentInstructions = context.instructions || "Respond to the GitHub Discussion as the repository bot.";
106
+ const botMessage = process.env.BOT_MESSAGE || "";
107
+
108
+ // ── Build lean prompt ──────────────────────────────────────────────
109
+ const promptParts = [
76
110
  "## Instructions",
77
111
  agentInstructions,
78
112
  "",
79
113
  "## Discussion Thread",
80
114
  `URL: ${discussionUrl}`,
81
- title ? `### ${title}` : "",
82
- body || "(no body)",
115
+ discussion.title ? `### ${discussion.title}` : "",
116
+ discussion.body || "(no body)",
83
117
  ];
84
118
 
85
119
  if (humanComments.length > 0) {
86
- parts.push("", "### Conversation History");
87
- for (const c of humanComments) {
88
- // Identify the triggering comment: match by node_id, then by createdAt, then fall back to latest
120
+ promptParts.push("", "### Recent Conversation");
121
+ for (const c of humanComments.slice(-5)) {
89
122
  let isTrigger = false;
90
123
  if (triggerComment?.nodeId && c.id) {
91
124
  isTrigger = c.id === triggerComment.nodeId;
@@ -95,19 +128,16 @@ function buildPrompt(discussionUrl, discussion, context, t, repoContext, trigger
95
128
  isTrigger = c === latestHumanComment;
96
129
  }
97
130
  const prefix = isTrigger ? ">>> **[TRIGGER — RESPOND TO THIS]** " : "";
98
- const nodeIdTag = c.id ? ` [node:${c.id}]` : "";
99
- parts.push(`${prefix}**${c.author?.login || "unknown"}** (${c.createdAt})${nodeIdTag}:\n${c.body}`);
131
+ promptParts.push(`${prefix}**${c.author?.login || "unknown"}** (${c.createdAt}):\n${c.body}`);
100
132
  }
101
133
  }
102
134
 
103
135
  if (lastBotReply) {
104
- parts.push("", "### Your Last Reply (DO NOT REPEAT THIS)", lastBotReply.body.substring(0, 500));
136
+ promptParts.push("", "### Your Last Reply (DO NOT REPEAT THIS)", lastBotReply.body.substring(0, 500));
105
137
  }
106
138
 
107
- // Include supervisor message if dispatched with context
108
- const botMessage = process.env.BOT_MESSAGE || "";
109
139
  if (botMessage) {
110
- parts.push(
140
+ promptParts.push(
111
141
  "",
112
142
  "## Triggering Request",
113
143
  "The supervisor dispatched you with the following message. This is your primary request — respond to it in the discussion thread:",
@@ -116,266 +146,170 @@ function buildPrompt(discussionUrl, discussion, context, t, repoContext, trigger
116
146
  );
117
147
  }
118
148
 
119
- parts.push(
149
+ promptParts.push(
120
150
  "",
121
151
  "## Repository Context",
122
152
  `### Mission\n${mission}`,
123
- contributing ? `### Contributing\n${contributing}` : "",
124
- `### Current Features\n${featureNames.join(", ") || "none"}`,
153
+ "",
154
+ "## Your Task",
155
+ "Read the discussion above. Use list_issues to see open issues if relevant.",
156
+ "Use list_discussions or search_discussions to find related conversations.",
157
+ "Compose a concise, engaging reply and call report_action with your reply and chosen action.",
158
+ "",
159
+ "## Available Actions (pass to report_action)",
160
+ "- `nop` — No action needed, just respond conversationally",
161
+ "- `request-supervisor` — Ask the supervisor to evaluate and act on a user request",
162
+ "- `create-feature` — Create a new feature (pass name as argument)",
163
+ "- `create-issue` — Create a new issue (pass title as argument)",
164
+ "- `mission-complete` — Declare mission complete",
165
+ "- `stop` — Halt automation",
166
+ "",
167
+ "**You MUST call report_action exactly once** with your reply text and action choice.",
125
168
  );
126
169
 
127
- // Add issue context
128
- if (repoContext?.issuesSummary?.length > 0) {
129
- parts.push(`### Open Issues (${repoContext.issuesSummary.length})`, repoContext.issuesSummary.join("\n"));
130
- }
170
+ const prompt = promptParts.filter(Boolean).join("\n");
131
171
 
132
- // Add actions-since-init context
133
- if (repoContext?.actionsSinceInit?.length > 0) {
134
- parts.push(
135
- `### Actions Since Last Init${repoContext.initTimestamp ? ` (${repoContext.initTimestamp})` : ""}`,
136
- ...repoContext.actionsSinceInit.map((a) => {
137
- let line = `- ${a.name}: ${a.conclusion} (${a.created}) [${a.commitSha}] ${a.commitMessage}`;
138
- if (a.prNumber) {
139
- line += ` — PR #${a.prNumber}: +${a.additions}/-${a.deletions} in ${a.changedFiles} file(s)`;
140
- }
141
- return line;
142
- }),
143
- );
144
- }
145
-
146
- parts.push(
147
- recentActivity ? `### Recent Activity\n${recentActivity}` : "",
148
- config.configToml ? `### Configuration (agentic-lib.toml)\n\`\`\`toml\n${config.configToml}\n\`\`\`` : "",
149
- config.packageJson ? `### Dependencies (package.json)\n\`\`\`json\n${config.packageJson}\n\`\`\`` : "",
150
- "",
151
- "## Actions",
152
- "Include exactly one action tag in your response. Only mention actions to the user when relevant.",
153
- "`[ACTION:request-supervisor] <free text>` — Ask the supervisor to evaluate and act on a user request",
154
- "`[ACTION:create-feature] <name>` — Create a new feature",
155
- "`[ACTION:update-feature] <name>` — Update an existing feature",
156
- "`[ACTION:delete-feature] <name>` — Delete a feature",
157
- "`[ACTION:create-issue] <title>` — Create a new issue",
158
- "`[ACTION:seed-repository]` — Reset to initial state",
159
- "`[ACTION:nop]` — No action needed, just respond conversationally",
160
- "`[ACTION:mission-complete]` — Declare mission complete",
161
- "`[ACTION:stop]` — Halt automation",
162
- );
172
+ const systemPrompt =
173
+ "You are this repository. Respond in first person. Be concise and engaging — never repeat what you said in your last reply. Adapt to the user's language level. Encourage experimentation and suggest interesting projects. When a user requests an action, pass it to the supervisor via request-supervisor action. Protect the mission: push back on requests that contradict it." +
174
+ NARRATIVE_INSTRUCTION;
163
175
 
164
- return parts.filter(Boolean).join("\n");
165
- }
176
+ // ── Shared mutable state for action results ──────────────────────
177
+ const actionResults = { action: "nop", actionArg: "", replyBody: "" };
178
+ const isSdkRepo = process.env.GITHUB_REPOSITORY === "xn-intenton-z2a/agentic-lib";
166
179
 
167
- async function postReply(octokit, nodeId, replyBody) {
168
- if (!nodeId) {
169
- core.warning("Cannot post reply: discussion node ID not available");
170
- return;
171
- }
172
- if (!replyBody) {
173
- core.warning("Cannot post reply: no reply content generated");
174
- return;
175
- }
176
- try {
177
- const mutation = `mutation($discussionId: ID!, $body: String!) {
178
- addDiscussionComment(input: { discussionId: $discussionId, body: $body }) {
179
- comment { url }
180
- }
181
- }`;
182
- const { addDiscussionComment } = await octokit.graphql(mutation, {
183
- discussionId: nodeId,
184
- body: replyBody,
185
- });
186
- core.info(`Posted reply to discussion: ${addDiscussionComment.comment.url}`);
187
- } catch (err) {
188
- core.warning(`Failed to post discussion reply: ${err.message}`);
189
- }
190
- }
180
+ // ── Create tools ─────────────────────────────────────────────────
181
+ const createTools = (defineTool, _wp, logger) => {
182
+ const discTools = createDiscussionTools(octokit, repo, defineTool, logger);
183
+ const ghTools = createGitHubTools(octokit, repo, defineTool, logger);
184
+
185
+ // Action tool — lets the model report its action and reply
186
+ const reportAction = defineTool("report_action", {
187
+ description: "Report the action taken in this discussion response and post your reply. Call this exactly once.",
188
+ parameters: {
189
+ type: "object",
190
+ properties: {
191
+ action: {
192
+ type: "string",
193
+ enum: ["nop", "request-supervisor", "create-feature", "update-feature", "delete-feature", "create-issue", "seed-repository", "mission-complete", "stop"],
194
+ description: "The action type",
195
+ },
196
+ argument: { type: "string", description: "Action argument (e.g. feature name, issue title, supervisor message)" },
197
+ reply: { type: "string", description: "The reply text to post to the discussion" },
198
+ },
199
+ required: ["action", "reply"],
200
+ },
201
+ handler: async ({ action, argument, reply }) => {
202
+ actionResults.action = action;
203
+ actionResults.actionArg = argument || "";
204
+ actionResults.replyBody = reply;
205
+
206
+ // Execute side effects
207
+ if (action === "mission-complete") {
208
+ const signal = [
209
+ "# Mission Complete",
210
+ "",
211
+ `- **Timestamp:** ${new Date().toISOString()}`,
212
+ `- **Detected by:** discussions`,
213
+ `- **Reason:** ${argument || "Declared via discussion bot"}`,
214
+ "",
215
+ "This file was created automatically. To restart transformations, delete this file or run `npx @xn-intenton-z2a/agentic-lib init --reseed`.",
216
+ ].join("\n");
217
+ writeFileSync("MISSION_COMPLETE.md", signal);
218
+ logger.info("Mission complete signal written (MISSION_COMPLETE.md)");
219
+ }
191
220
 
192
- /**
193
- * Respond to a GitHub Discussion using the Copilot SDK.
194
- *
195
- * @param {Object} context - Task context from index.js
196
- * @returns {Promise<Object>} Result with outcome, action, tokensUsed, model
197
- */
198
- export async function discussions(context) {
199
- const { octokit, model, discussionUrl, repo, config } = context;
200
- const t = config?.tuning || {};
221
+ if (action === "create-issue" && argument) {
222
+ try {
223
+ const { data: issue } = await octokit.rest.issues.create({
224
+ ...repo,
225
+ title: argument,
226
+ labels: ["automated", "enhancement"],
227
+ });
228
+ logger.info(`Created issue #${issue.number}: ${argument}`);
229
+ } catch (err) {
230
+ logger.warning(`Failed to create issue: ${err.message}`);
231
+ }
232
+ }
201
233
 
202
- if (!discussionUrl) {
203
- throw new Error("discussions task requires discussion-url input");
204
- }
234
+ if (action === "request-supervisor") {
235
+ logger.info(`Supervisor requested: ${argument || "Discussion bot referral"} (dispatch handled by bot workflow)`);
236
+ }
205
237
 
206
- // Gather repo context: issues + actions since init
207
- const repoContext = { issuesSummary: [], actionsSinceInit: [], initTimestamp: null };
208
- if (octokit && repo) {
209
- try {
210
- const { data: openIssues } = await octokit.rest.issues.listForRepo({
211
- ...repo, state: "open", per_page: 10, sort: "created", direction: "asc",
212
- });
213
- const initTimestampForIssues = config?.init?.timestamp || null;
214
- const initEpochForIssues = initTimestampForIssues ? new Date(initTimestampForIssues).getTime() : 0;
215
- repoContext.issuesSummary = openIssues
216
- .filter((i) => !i.pull_request && (initEpochForIssues <= 0 || new Date(i.created_at).getTime() >= initEpochForIssues))
217
- .map((i) => {
218
- const labels = i.labels.map((l) => l.name).join(", ");
219
- return `#${i.number}: ${i.title} [${labels || "no labels"}]`;
220
- });
221
- } catch (err) {
222
- core.warning(`Could not fetch issues for discussion context: ${err.message}`);
223
- }
238
+ if (action === "stop") {
239
+ if (isSdkRepo) {
240
+ logger.info("Skipping schedule dispatch — running in SDK repo");
241
+ } else {
242
+ try {
243
+ await octokit.rest.actions.createWorkflowDispatch({
244
+ ...repo,
245
+ workflow_id: "agentic-lib-schedule.yml",
246
+ ref: "main",
247
+ inputs: { frequency: "off" },
248
+ });
249
+ logger.info("Automation stopped via discussions bot");
250
+ } catch (err) {
251
+ logger.warning(`Failed to stop automation: ${err.message}`);
252
+ }
253
+ }
254
+ }
224
255
 
225
- const initTimestamp = config?.init?.timestamp || null;
226
- repoContext.initTimestamp = initTimestamp;
227
- try {
228
- const { data: runs } = await octokit.rest.actions.listWorkflowRunsForRepo({
229
- ...repo, per_page: 20,
230
- });
231
- const initDate = initTimestamp ? new Date(initTimestamp) : null;
232
- const relevantRuns = initDate
233
- ? runs.workflow_runs.filter((r) => new Date(r.created_at) >= initDate)
234
- : runs.workflow_runs.slice(0, 10);
235
-
236
- for (const run of relevantRuns) {
237
- const commit = run.head_commit;
238
- const entry = {
239
- name: run.name,
240
- conclusion: run.conclusion || run.status,
241
- created: run.created_at,
242
- commitMessage: commit?.message?.split("\n")[0] || "",
243
- commitSha: run.head_sha?.substring(0, 7) || "",
244
- };
245
- if (run.head_branch?.startsWith("agentic-lib-issue-")) {
256
+ // Post the reply to the discussion
257
+ if (discussion.nodeId && reply) {
246
258
  try {
247
- const { data: prs } = await octokit.rest.pulls.list({
248
- ...repo, head: `${repo.owner}:${run.head_branch}`, state: "all", per_page: 1,
259
+ const mutation = `mutation($discussionId: ID!, $body: String!) {
260
+ addDiscussionComment(input: { discussionId: $discussionId, body: $body }) {
261
+ comment { url }
262
+ }
263
+ }`;
264
+ const { addDiscussionComment } = await octokit.graphql(mutation, {
265
+ discussionId: discussion.nodeId,
266
+ body: reply,
249
267
  });
250
- if (prs.length > 0) {
251
- entry.prNumber = prs[0].number;
252
- entry.additions = prs[0].additions;
253
- entry.deletions = prs[0].deletions;
254
- entry.changedFiles = prs[0].changed_files;
255
- }
256
- } catch { /* ignore */ }
268
+ logger.info(`Posted reply: ${addDiscussionComment.comment.url}`);
269
+ } catch (err) {
270
+ logger.warning(`Failed to post discussion reply: ${err.message}`);
271
+ }
257
272
  }
258
- repoContext.actionsSinceInit.push(entry);
259
- }
260
- } catch (err) {
261
- core.warning(`Could not fetch workflow runs for discussion context: ${err.message}`);
262
- }
263
- }
264
273
 
265
- const discussion = await fetchDiscussion(octokit, discussionUrl, t.discussionComments || 10);
274
+ return { textResultForLlm: `Action recorded: ${action}. Reply posted.` };
275
+ },
276
+ });
266
277
 
267
- // Filter discussion comments to only those after the most recent init
268
- const initTs = config?.init?.timestamp || null;
269
- if (initTs && discussion.comments.length > 0) {
270
- const initDate = new Date(initTs);
271
- discussion.comments = discussion.comments.filter((c) => new Date(c.createdAt) >= initDate);
272
- }
278
+ return [...discTools, ...ghTools, reportAction];
279
+ };
273
280
 
274
- // Extract trigger comment info from multiple sources:
275
- // 1. Event payload (discussion_comment event) — most reliable
276
- // 2. Explicit inputs (comment-node-id, comment-created-at) for workflow_dispatch
277
- // 3. Fall back to latest human comment (handled in buildPrompt)
278
- const triggerComment = {};
279
- const eventComment = context.github?.payload?.comment;
280
- if (eventComment) {
281
- triggerComment.nodeId = eventComment.node_id || "";
282
- triggerComment.createdAt = eventComment.created_at || "";
283
- triggerComment.body = eventComment.body || "";
284
- triggerComment.login = eventComment.user?.login || "";
285
- core.info(`Trigger comment from event payload: ${triggerComment.login} at ${triggerComment.createdAt}`);
286
- }
287
- // Explicit inputs override event payload (workflow_dispatch or workflow_call may pass these)
288
- if (context.commentNodeId) triggerComment.nodeId = context.commentNodeId;
289
- if (context.commentCreatedAt) triggerComment.createdAt = context.commentCreatedAt;
281
+ // ── Run hybrid session ───────────────────────────────────────────
282
+ const attachments = [];
283
+ if (logFilePath) attachments.push({ type: "file", path: logFilePath });
284
+ if (screenshotFilePath) attachments.push({ type: "file", path: screenshotFilePath });
290
285
 
291
- const prompt = buildPrompt(discussionUrl, discussion, context, t, repoContext, triggerComment);
292
- const { content, tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
286
+ const result = await runCopilotSession({
287
+ workspacePath: process.cwd(),
293
288
  model,
294
- systemMessage:
295
- "You are this repository. Respond in first person. Be concise and engaging — never repeat what you said in your last reply. Adapt to the user's language level. Encourage experimentation and suggest interesting projects. When a user requests an action, pass it to the supervisor via [ACTION:request-supervisor]. Protect the mission: push back on requests that contradict it.",
296
- prompt,
297
- writablePaths: [],
298
289
  tuning: t,
290
+ agentPrompt: systemPrompt,
291
+ userPrompt: prompt,
292
+ writablePaths: [],
293
+ createTools,
294
+ attachments,
295
+ excludedTools: ["write_file", "run_command", "run_tests", "dispatch_workflow"],
296
+ logger: { info: core.info, warning: core.warning, error: core.error, debug: core.debug },
299
297
  });
300
298
 
301
- const actionMatch = content.match(/\[ACTION:(\S+?)\](.+)?/);
302
- const action = actionMatch ? actionMatch[1] : "nop";
303
- const actionArg = actionMatch && actionMatch[2] ? actionMatch[2].trim() : "";
304
- const replyBody = content.replace(/\[ACTION:\S+?\].+/, "").trim();
305
-
306
- core.info(`Discussion bot action: ${action}, arg: ${actionArg}`);
307
-
308
- // Write MISSION_COMPLETE.md signal when bot declares mission complete
309
- if (action === "mission-complete") {
310
- const signal = [
311
- "# Mission Complete",
312
- "",
313
- `- **Timestamp:** ${new Date().toISOString()}`,
314
- `- **Detected by:** discussions`,
315
- `- **Reason:** ${actionArg || "Declared via discussion bot"}`,
316
- "",
317
- "This file was created automatically. To restart transformations, delete this file or run `npx @xn-intenton-z2a/agentic-lib init --reseed`.",
318
- ].join("\n");
319
- writeFileSync("MISSION_COMPLETE.md", signal);
320
- core.info("Mission complete signal written (MISSION_COMPLETE.md)");
321
- }
322
-
323
- // Create issue when bot requests it
324
- if (action === "create-issue" && actionArg) {
325
- try {
326
- const { data: issue } = await octokit.rest.issues.create({
327
- ...context.repo,
328
- title: actionArg,
329
- labels: ["automated", "enhancement"],
330
- });
331
- core.info(`Created issue #${issue.number}: ${actionArg}`);
332
- } catch (err) {
333
- core.warning(`Failed to create issue: ${err.message}`);
334
- }
335
- }
336
-
337
- // Guard: never dispatch workflows from the SDK repo itself (agentic-lib)
338
- const isSdkRepo = process.env.GITHUB_REPOSITORY === "xn-intenton-z2a/agentic-lib";
339
-
340
- // Request supervisor evaluation — dispatch is handled by the bot workflow's
341
- // dispatch-supervisor job, so we just log the action here to avoid double dispatch.
342
- if (action === "request-supervisor") {
343
- core.info(`Supervisor requested with message: ${actionArg || "Discussion bot referral"} (dispatch handled by bot workflow)`);
344
- }
345
-
346
- // Stop automation
347
- if (action === "stop") {
348
- if (isSdkRepo) {
349
- core.info("Skipping schedule dispatch — running in SDK repo");
350
- } else {
351
- try {
352
- await octokit.rest.actions.createWorkflowDispatch({
353
- ...context.repo,
354
- workflow_id: "agentic-lib-schedule.yml",
355
- ref: "main",
356
- inputs: { frequency: "off" },
357
- });
358
- core.info("Automation stopped via discussions bot");
359
- } catch (err) {
360
- core.warning(`Failed to stop automation: ${err.message}`);
361
- }
362
- }
363
- }
364
-
365
- await postReply(octokit, discussion.nodeId, replyBody);
299
+ core.info(`Discussion bot action: ${actionResults.action}, arg: ${actionResults.actionArg}`);
366
300
 
367
- const argSuffix = actionArg ? ` (${actionArg})` : "";
301
+ const argSuffix = actionResults.actionArg ? ` (${actionResults.actionArg})` : "";
368
302
  return {
369
- outcome: `discussion-${action}`,
370
- tokensUsed,
371
- inputTokens,
372
- outputTokens,
373
- cost,
303
+ outcome: `discussion-${actionResults.action}`,
304
+ tokensUsed: result.tokensIn + result.tokensOut,
305
+ inputTokens: result.tokensIn,
306
+ outputTokens: result.tokensOut,
307
+ cost: 0,
374
308
  model,
375
- details: `Action: ${action}${argSuffix}\nReply: ${replyBody.substring(0, 200)}`,
376
- narrative: `Responded to discussion with action ${action}${argSuffix}.`,
377
- action,
378
- actionArg,
379
- replyBody,
309
+ details: `Action: ${actionResults.action}${argSuffix}\nReply: ${(actionResults.replyBody || "").substring(0, 200)}`,
310
+ narrative: result.narrative || extractNarrative(result.agentMessage, `Responded to discussion with action ${actionResults.action}${argSuffix}.`),
311
+ action: actionResults.action,
312
+ actionArg: actionResults.actionArg,
313
+ replyBody: actionResults.replyBody,
380
314
  };
381
315
  }