edsger 0.64.0 → 0.65.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.
|
@@ -4,12 +4,30 @@
|
|
|
4
4
|
* its own line and free of logger formatting so it parses cleanly.
|
|
5
5
|
*/
|
|
6
6
|
export declare const SESSION_ID_MARKER = "__EDSGER_SESSION_ID__=";
|
|
7
|
+
/**
|
|
8
|
+
* Marker emitted on stdout for each background `cli_*` run still in flight when
|
|
9
|
+
* the turn ends. The desktop main process parses these, watches each run to
|
|
10
|
+
* completion (the turn process itself exits immediately), and then re-engages
|
|
11
|
+
* the agent via `--run-finished` so it reports results without the user having
|
|
12
|
+
* to ask. The JSON payload is `{ run_id, pid, log_path, command }`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CLI_RUN_MARKER = "__EDSGER_CLI_RUN__=";
|
|
7
15
|
export interface SessionTurnCliOptions {
|
|
8
16
|
channelId: string;
|
|
9
17
|
productId: string;
|
|
10
18
|
repositoryIds?: string[];
|
|
11
19
|
/** SDK session id from a previous turn, to resume the same conversation. */
|
|
12
20
|
resumeSessionId?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Follow-up turn triggered by the desktop watcher when a background `cli_*`
|
|
23
|
+
* run finishes. When set, the turn is driven by a synthetic prompt instead of
|
|
24
|
+
* the channel's pending human messages: the agent inspects what the run
|
|
25
|
+
* produced and reports it to the user.
|
|
26
|
+
*/
|
|
27
|
+
runFinished?: {
|
|
28
|
+
runId: string;
|
|
29
|
+
command?: string;
|
|
30
|
+
};
|
|
13
31
|
verbose?: boolean;
|
|
14
32
|
}
|
|
15
33
|
export declare function runSessionTurnCommand(options: SessionTurnCliOptions): Promise<void>;
|
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
* its own line and free of logger formatting so it parses cleanly.
|
|
5
5
|
*/
|
|
6
6
|
export const SESSION_ID_MARKER = '__EDSGER_SESSION_ID__=';
|
|
7
|
+
/**
|
|
8
|
+
* Marker emitted on stdout for each background `cli_*` run still in flight when
|
|
9
|
+
* the turn ends. The desktop main process parses these, watches each run to
|
|
10
|
+
* completion (the turn process itself exits immediately), and then re-engages
|
|
11
|
+
* the agent via `--run-finished` so it reports results without the user having
|
|
12
|
+
* to ask. The JSON payload is `{ run_id, pid, log_path, command }`.
|
|
13
|
+
*/
|
|
14
|
+
export const CLI_RUN_MARKER = '__EDSGER_CLI_RUN__=';
|
|
7
15
|
/**
|
|
8
16
|
* `edsger session-turn <channelId>` — run one conversational agent turn for a
|
|
9
17
|
* chat session (an ai_assistant channel bound to a product).
|
|
@@ -17,7 +25,7 @@ export const SESSION_ID_MARKER = '__EDSGER_SESSION_ID__=';
|
|
|
17
25
|
* there is no fixed pipeline.
|
|
18
26
|
*/
|
|
19
27
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
20
|
-
import { buildSessionAgentOptions, buildSessionSystemPrompt, buildSessionUserPrompt, createSessionMcpServer, loadExternalMcpServers, SESSION_MAX_TURNS, } from 'edsger-tools';
|
|
28
|
+
import { buildSessionAgentOptions, buildSessionSystemPrompt, buildSessionUserPrompt, createSessionMcpServer, listActiveCliRuns, loadExternalMcpServers, SESSION_MAX_TURNS, } from 'edsger-tools';
|
|
21
29
|
import { callMcpEndpoint } from '../../api/mcp-client.js';
|
|
22
30
|
import { DEFAULT_MODEL } from '../../constants.js';
|
|
23
31
|
import { getToolDeps } from '../../tools/bootstrap.js';
|
|
@@ -46,23 +54,44 @@ async function prepareSessionWorkspace(opts) {
|
|
|
46
54
|
};
|
|
47
55
|
}
|
|
48
56
|
export async function runSessionTurnCommand(options) {
|
|
49
|
-
const { channelId, productId, repositoryIds = [], resumeSessionId, verbose = false, } = options;
|
|
57
|
+
const { channelId, productId, repositoryIds = [], resumeSessionId, runFinished, verbose = false, } = options;
|
|
50
58
|
// Emit the SDK session id so the desktop can persist it for the next turn.
|
|
51
59
|
const emitSessionId = (id) => {
|
|
52
60
|
if (id) {
|
|
53
61
|
process.stdout.write(`\n${SESSION_ID_MARKER}${id}\n`);
|
|
54
62
|
}
|
|
55
63
|
};
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
// Emit a marker for every background cli_* run still in flight, so the
|
|
65
|
+
// desktop watcher can poll each to completion and re-engage the agent.
|
|
66
|
+
const emitActiveCliRuns = () => {
|
|
67
|
+
for (const run of listActiveCliRuns()) {
|
|
68
|
+
const payload = JSON.stringify({
|
|
69
|
+
run_id: run.run_id,
|
|
70
|
+
pid: run.pid,
|
|
71
|
+
log_path: run.log_path,
|
|
72
|
+
command: run.command,
|
|
73
|
+
});
|
|
74
|
+
process.stdout.write(`\n${CLI_RUN_MARKER}${payload}\n`);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
// 1. Decide what drives this turn. A normal turn replays the channel's
|
|
78
|
+
// pending human messages; a watcher-triggered follow-up replays a
|
|
79
|
+
// synthetic prompt about the background run that just finished.
|
|
80
|
+
let messages = [];
|
|
81
|
+
if (runFinished) {
|
|
82
|
+
logInfo(`Reporting completion of background run ${runFinished.runId} for session ${channelId}`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const pendingResult = (await callMcpEndpoint('chat/messages/pending', {
|
|
86
|
+
channel_id: channelId,
|
|
87
|
+
}));
|
|
88
|
+
messages = pendingResult.messages ?? [];
|
|
89
|
+
if (messages.length === 0) {
|
|
90
|
+
logInfo('No pending messages for this session — nothing to do.');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
logInfo(`Processing ${messages.length} message(s) for session ${channelId}`);
|
|
64
94
|
}
|
|
65
|
-
logInfo(`Processing ${messages.length} message(s) for session ${channelId}`);
|
|
66
95
|
// 2. Clone the product's in-scope repositories into a local session
|
|
67
96
|
// directory so the agent's Read/Grep/Glob can inspect the real code.
|
|
68
97
|
const { sessionDir, repoScopeNote } = await prepareSessionWorkspace({
|
|
@@ -89,7 +118,9 @@ export async function runSessionTurnCommand(options) {
|
|
|
89
118
|
repoScopeNote,
|
|
90
119
|
externalMcpNames: external.names,
|
|
91
120
|
});
|
|
92
|
-
const userPrompt =
|
|
121
|
+
const userPrompt = runFinished
|
|
122
|
+
? buildRunFinishedPrompt(runFinished)
|
|
123
|
+
: buildSessionUserPrompt(messages);
|
|
93
124
|
// 4. Run one SDK turn. Resume the prior SDK session when we have its id so
|
|
94
125
|
// the conversation (and prompt cache) carries across turns. Point the
|
|
95
126
|
// agent's cwd at the session directory so its read-only file tools resolve
|
|
@@ -137,6 +168,7 @@ export async function runSessionTurnCommand(options) {
|
|
|
137
168
|
metadata: {},
|
|
138
169
|
}).catch(() => undefined);
|
|
139
170
|
emitSessionId(sdkSessionId);
|
|
171
|
+
emitActiveCliRuns();
|
|
140
172
|
await markProcessed(messages);
|
|
141
173
|
return;
|
|
142
174
|
}
|
|
@@ -153,12 +185,27 @@ export async function runSessionTurnCommand(options) {
|
|
|
153
185
|
logError(`Failed to post reply: ${e instanceof Error ? e.message : String(e)}`);
|
|
154
186
|
});
|
|
155
187
|
}
|
|
156
|
-
// 6. Mark the triggering messages processed so they aren't re-run.
|
|
188
|
+
// 6. Mark the triggering messages processed so they aren't re-run. (No-op for
|
|
189
|
+
// a watcher follow-up, which has no triggering human messages.)
|
|
157
190
|
await markProcessed(messages);
|
|
158
|
-
// 7. Emit the SDK session id so the desktop persists it for the next turn
|
|
191
|
+
// 7. Emit the SDK session id so the desktop persists it for the next turn,
|
|
192
|
+
// plus a marker for any background run launched this turn so the watcher
|
|
193
|
+
// keeps following it to completion.
|
|
159
194
|
emitSessionId(sdkSessionId);
|
|
195
|
+
emitActiveCliRuns();
|
|
160
196
|
logSuccess('Session turn complete.');
|
|
161
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* The synthetic user prompt for a watcher-triggered follow-up turn. Steers the
|
|
200
|
+
* agent to inspect what the finished run produced and report it — explicitly
|
|
201
|
+
* NOT to ask the user whether it should check.
|
|
202
|
+
*/
|
|
203
|
+
function buildRunFinishedPrompt(runFinished) {
|
|
204
|
+
const which = runFinished.command
|
|
205
|
+
? `the \`${runFinished.command}\` analysis (run ${runFinished.runId})`
|
|
206
|
+
: `a background analysis you launched earlier (run ${runFinished.runId})`;
|
|
207
|
+
return `${which} just finished. Inspect what it produced — call get_cli_run with run_id "${runFinished.runId}" for the log tail, then use the list_* / get_* tools (e.g. list_issues) to see the findings it filed. Summarize the results for the user via send_chat_message. Do not ask whether you should check — just report what you found. If nothing of note was produced, say so briefly.`;
|
|
208
|
+
}
|
|
162
209
|
async function markProcessed(messages) {
|
|
163
210
|
for (const m of messages) {
|
|
164
211
|
await callMcpEndpoint('chat/messages/mark_processed', {
|
package/dist/index.js
CHANGED
|
@@ -441,6 +441,8 @@ program
|
|
|
441
441
|
.requiredOption('--product <productId>', 'Product the session is bound to')
|
|
442
442
|
.option('--repos <ids>', 'Comma-separated repository IDs the agent may touch', '')
|
|
443
443
|
.option('--resume <sessionId>', 'SDK session id from a previous turn to resume the same conversation')
|
|
444
|
+
.option('--run-finished <runId>', 'Follow-up turn: report the results of a background cli_* run that finished')
|
|
445
|
+
.option('--run-command <command>', 'The command name of the finished run (used with --run-finished)')
|
|
444
446
|
.option('-v, --verbose', 'Verbose output')
|
|
445
447
|
.action(async (channelId, opts) => {
|
|
446
448
|
try {
|
|
@@ -452,6 +454,9 @@ program
|
|
|
452
454
|
.map((s) => s.trim())
|
|
453
455
|
.filter(Boolean),
|
|
454
456
|
resumeSessionId: opts.resume,
|
|
457
|
+
runFinished: opts.runFinished
|
|
458
|
+
? { runId: opts.runFinished, command: opts.runCommand }
|
|
459
|
+
: undefined,
|
|
455
460
|
verbose: opts.verbose,
|
|
456
461
|
});
|
|
457
462
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edsger",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"edsger": "dist/index.js"
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"cosmiconfig": "^9.0.0",
|
|
52
52
|
"dotenv": "^16.4.5",
|
|
53
53
|
"edsger-contract": "0.7.0",
|
|
54
|
-
"edsger-tools": "0.
|
|
54
|
+
"edsger-tools": "0.8.0",
|
|
55
55
|
"gray-matter": "^4.0.3",
|
|
56
56
|
"zod": "^4.0.0"
|
|
57
57
|
},
|