@zgeoff/atc 2.41.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -38,6 +38,7 @@ export const ID_RULES: Readonly<Record<string, ReadonlyMap<string, IDRule>>> = {
|
|
|
38
38
|
'session.update': new Map(),
|
|
39
39
|
'session.kill': new Map(),
|
|
40
40
|
'session.ack': new Map(),
|
|
41
|
+
'session.forget': new Map([['confirmToken', 'opaque']]),
|
|
41
42
|
'session.list': buildPrefixedRules('sessions[].', DESCRIPTOR_RULES),
|
|
42
43
|
'session.spawn': buildPrefixedRules('session.', DESCRIPTOR_RULES),
|
|
43
44
|
'session.get': buildPrefixedRules('session.', DESCRIPTOR_RULES),
|
|
@@ -68,13 +69,15 @@ export const ID_RULES: Readonly<Record<string, ReadonlyMap<string, IDRule>>> = {
|
|
|
68
69
|
/**
|
|
69
70
|
* The rule for every id-bearing field of an error's `data`, whatever the
|
|
70
71
|
* method: `effectRef` holds the session or message an uncertain or
|
|
71
|
-
* conflicting keyed request made
|
|
72
|
+
* conflicting keyed request made, and `host` holds the session that owns
|
|
73
|
+
* a host a refusal concerns.
|
|
72
74
|
*/
|
|
73
75
|
export const ERROR_DATA_RULES: ReadonlyMap<string, IDRule> = new Map([
|
|
74
76
|
['effectRef', 'id'],
|
|
75
77
|
['session', 'id'],
|
|
76
78
|
['message', 'id'],
|
|
77
79
|
['parent', 'id'],
|
|
80
|
+
['host', 'id'],
|
|
78
81
|
]);
|
|
79
82
|
|
|
80
83
|
function buildPrefixedRules(
|
package/src/mcp/mcp-tools.ts
CHANGED
|
@@ -474,6 +474,31 @@ export const MCP_TOOLS: readonly MCPToolDefinition[] = [
|
|
|
474
474
|
description: 'Kill a session. A second kill on a dead session removes it from the list.',
|
|
475
475
|
inputSchema: SESSION_INPUT,
|
|
476
476
|
},
|
|
477
|
+
{
|
|
478
|
+
name: 'atc_session_forget',
|
|
479
|
+
annotations: DESTRUCTIVE,
|
|
480
|
+
scope: 'kill',
|
|
481
|
+
description:
|
|
482
|
+
'Forget a session for good: it leaves the list. A live local sub-session of the session is not stopped: it stays alive and moves to the top level. On a target that can destroy its host (an imp), the first call changes nothing and returns { confirmToken, expiresAt }, a token good for 60 seconds; a second call with that token destroys the host and returns { forgotten: true, destroyed: true }, except that a sub-session on the imp host of its parent does not destroy that host and returns destroyed: false. On any other target one call forgets and returns { forgotten: true, destroyed: false }. A live session is refused unless stop is true, which stops it as part of the forget. A pinned session, or a sub-session of a pinned session, is refused: unpin it with atc_session_update first.',
|
|
483
|
+
inputSchema: {
|
|
484
|
+
type: 'object',
|
|
485
|
+
properties: {
|
|
486
|
+
session: { type: 'string', description: 'The atc session id' },
|
|
487
|
+
confirmToken: {
|
|
488
|
+
type: 'string',
|
|
489
|
+
minLength: 1,
|
|
490
|
+
description: 'The token an earlier call on the same session returned',
|
|
491
|
+
},
|
|
492
|
+
stop: {
|
|
493
|
+
type: 'boolean',
|
|
494
|
+
description: 'Stop the session when it is live; omit or false refuses a live session',
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
required: ['session'],
|
|
498
|
+
additionalProperties: false,
|
|
499
|
+
},
|
|
500
|
+
requires: { tool: 'session.forget' },
|
|
501
|
+
},
|
|
477
502
|
{
|
|
478
503
|
name: 'atc_session_ack',
|
|
479
504
|
annotations: ADDITIVE,
|
|
@@ -10,7 +10,7 @@ const FEATURE_USES: Readonly<Record<DaemonFeature, string>> = {
|
|
|
10
10
|
'message.idempotency': "atc_session_message's idempotencyKey",
|
|
11
11
|
'message.turn': "atc_message_get's turn and answeredWith",
|
|
12
12
|
'message.wait': "atc_message_get's waitMs",
|
|
13
|
-
'session.forget': '
|
|
13
|
+
'session.forget': 'atc_session_forget',
|
|
14
14
|
'session.locator': "a session's locator",
|
|
15
15
|
'session.submit': 'atc_session_input',
|
|
16
16
|
'spawn.idempotency': "atc_session_spawn's idempotencyKey",
|
package/src/mcp/run-tool.ts
CHANGED
|
@@ -146,6 +146,22 @@ export function runTool(
|
|
|
146
146
|
|
|
147
147
|
return { text: 'killed', structured: null };
|
|
148
148
|
})
|
|
149
|
+
.with('atc_session_forget', async () => {
|
|
150
|
+
await requireForgettable(caller, args['session'], args['stop'] === true);
|
|
151
|
+
|
|
152
|
+
const ok = await caller.sendRequest(
|
|
153
|
+
'session.forget',
|
|
154
|
+
{
|
|
155
|
+
session: args['session'],
|
|
156
|
+
...(typeof args['confirmToken'] === 'string'
|
|
157
|
+
? { confirmToken: args['confirmToken'] }
|
|
158
|
+
: {}),
|
|
159
|
+
},
|
|
160
|
+
['session.forget'],
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return buildObjectResult(ok);
|
|
164
|
+
})
|
|
149
165
|
.with('atc_session_ack', async () => {
|
|
150
166
|
await caller.sendRequest('session.ack', { session: args['session'] });
|
|
151
167
|
|
|
@@ -269,6 +285,37 @@ function buildObjectResult(value: unknown): ToolResult {
|
|
|
269
285
|
};
|
|
270
286
|
}
|
|
271
287
|
|
|
288
|
+
// A forget is for good, so it reads the session first: an unseen or unknown
|
|
289
|
+
// session fails here before the daemon mints a token, and a pinned or live
|
|
290
|
+
// session is refused with the step that unblocks it.
|
|
291
|
+
async function requireForgettable(
|
|
292
|
+
caller: FleetCaller,
|
|
293
|
+
session: unknown,
|
|
294
|
+
stops: boolean,
|
|
295
|
+
): Promise<void> {
|
|
296
|
+
const got = await caller.sendRequest('session.get', { session });
|
|
297
|
+
|
|
298
|
+
const descriptor = isRecord(got['session']) ? got['session'] : {};
|
|
299
|
+
const parent = descriptor['parent'];
|
|
300
|
+
let pinned = descriptor['pinned'] === true;
|
|
301
|
+
|
|
302
|
+
if (!pinned && typeof parent === 'string') {
|
|
303
|
+
const owner = await caller.sendRequest('session.get', { session: parent });
|
|
304
|
+
|
|
305
|
+
pinned = isRecord(owner['session']) && owner['session']['pinned'] === true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (pinned) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
'session_pinned: the session is pinned, or is a sub-session of a pinned session. Unpin it with atc_session_update before forgetting it.',
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (descriptor['alive'] === true && !stops) {
|
|
315
|
+
throw new Error('session_live: the session is live. Pass stop: true to stop and forget it.');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
272
319
|
// The inherited id can point at a session another daemon hosts, or one
|
|
273
320
|
// this daemon no longer lists; the spawn then lands top-level instead of
|
|
274
321
|
// failing the tool call. The top-level spawn has a different payload, so it
|