@ours.network/fleet 0.15.3 → 0.15.5
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/README.md +54 -19
- package/dist/application/fleet-query-service.js +3 -0
- package/dist/application/model-catalog.d.ts +20 -0
- package/dist/application/model-catalog.js +57 -0
- package/dist/application/role-creation-service.d.ts +7 -0
- package/dist/application/role-creation-service.js +21 -4
- package/dist/application/role-removal-service.d.ts +32 -0
- package/dist/application/role-removal-service.js +87 -0
- package/dist/application/role-repository.js +13 -1
- package/dist/application/session-control.d.ts +74 -0
- package/dist/application/session-control.js +66 -1
- package/dist/application/types.d.ts +18 -0
- package/dist/briefing.js +10 -1
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +4 -1
- package/dist/config.js +3 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +14 -5
- package/dist/fleet-proxy.js +3 -1
- package/dist/harness/claude-code.js +20 -3
- package/dist/harness/codex.js +14 -2
- package/dist/harness/types.d.ts +6 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/owner-channel/attachments.d.ts +11 -1
- package/dist/owner-channel/attachments.js +24 -3
- package/dist/owner-channel/channel.d.ts +2 -0
- package/dist/owner-channel/channel.js +134 -12
- package/dist/owner-channel/state.d.ts +1 -0
- package/dist/owner-channel/state.js +6 -3
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.js +7 -0
- package/dist/runner.js +2 -0
- package/dist/session/acp.d.ts +66 -1
- package/dist/session/acp.js +427 -22
- package/dist/session/arbiter.d.ts +10 -1
- package/dist/session/arbiter.js +24 -0
- package/dist/session/control.d.ts +28 -2
- package/dist/session/control.js +145 -5
- package/dist/session/conversation-normalizer.d.ts +34 -0
- package/dist/session/conversation-normalizer.js +356 -0
- package/dist/session/conversation-store.d.ts +88 -0
- package/dist/session/conversation-store.js +347 -0
- package/dist/session/conversation-types.d.ts +274 -0
- package/dist/session/conversation-types.js +1 -0
- package/dist/session/events.js +6 -1
- package/dist/session/types.d.ts +49 -0
- package/dist/spawn.d.ts +1 -0
- package/dist/spawn.js +9 -4
- package/dist/web/auth.d.ts +1 -1
- package/dist/web/fleet-config-service.d.ts +47 -0
- package/dist/web/fleet-config-service.js +204 -0
- package/dist/web/runtime.js +14 -1
- package/dist/web/server.d.ts +6 -0
- package/dist/web/server.js +181 -9
- package/dist/web/topology.d.ts +31 -0
- package/dist/web/topology.js +61 -0
- package/dist/web-app/assets/{TerminalView-DMoT8udI.js → TerminalView-hZpyUFY_.js} +1 -1
- package/dist/web-app/assets/index-COg4Azq1.css +1 -0
- package/dist/web-app/assets/index-Cde9auW0.js +10 -0
- package/dist/web-app/index.html +2 -2
- package/package.json +1 -1
- package/dist/web-app/assets/index-B-jtLAkp.css +0 -1
- package/dist/web-app/assets/index-B6T8JLSd.js +0 -9
|
@@ -10,7 +10,7 @@ import { OursMcpClient } from './mcp.js';
|
|
|
10
10
|
import { ownerNotices } from './notices.js';
|
|
11
11
|
import { DuplicateSendError, OwnerAuthorizationState, OwnerChannelState, OwnerConversationState, } from './state.js';
|
|
12
12
|
import { OwnerTaskState, ownerTaskAuditId, ownerTaskDigest, } from './tasks.js';
|
|
13
|
-
import { AttachmentRecoveryState, admitAttachments, cleanupAttachmentRoot, parseIncomingAttachments, parseRetrievedAttachments, prepareAttachmentDirectory, recoveredAttachment, removeRequestDirectory, safeField, validateAttachmentSelection, } from './attachments.js';
|
|
13
|
+
import { AttachmentRecoveryState, admitAttachments, cleanupAttachmentRoot, parseIncomingAttachments, parseRetrievedAttachments, prepareAttachmentDirectory, recoveredAttachment, removeRequestDirectory, safeField, validateAttachmentSelection, validateAttachmentRelaySelection, } from './attachments.js';
|
|
14
14
|
import { acquireOwnerBinderLease, OWNER_BIND_HANDOFF_TIMEOUT_MS, } from './binder.js';
|
|
15
15
|
const OWNER_UPDATE_MIN_INTERVAL_MS = 5_000;
|
|
16
16
|
const OWNER_UPDATE_MAX_COUNT = 20;
|
|
@@ -19,6 +19,11 @@ const OWNER_UPDATE_MAX_BYTES = 1_024;
|
|
|
19
19
|
const PROACTIVE_MESSAGE_MAX_CHARS = 4_000;
|
|
20
20
|
const PROACTIVE_MESSAGE_MAX_BYTES = 16_384;
|
|
21
21
|
const RELAY_NACK_MEMORY = 512;
|
|
22
|
+
const COMMENTARY_FLUSH_MS = 750;
|
|
23
|
+
const COMMENTARY_MAX_CHARS = 1_600;
|
|
24
|
+
const COMMENTARY_MAX_BYTES = 6_400;
|
|
25
|
+
const COMMENTARY_MAX_UPDATES = 32;
|
|
26
|
+
const COMMENTARY_DEDUPE_LIMIT = 512;
|
|
22
27
|
/** A relay attempt that failed only because no owner route exists yet. */
|
|
23
28
|
class RelayUnroutableError extends Error {
|
|
24
29
|
}
|
|
@@ -671,7 +676,8 @@ export class OwnerChannel {
|
|
|
671
676
|
const queued = await this.options.session.queuePrompt(this.ownerAttachmentPrompt(sender, originWireId, requestId, outbox, admitted, group.caption), {
|
|
672
677
|
interrupt: this.options.config.interrupt,
|
|
673
678
|
...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
|
|
674
|
-
origin: { kind: 'owner', requestId
|
|
679
|
+
origin: { kind: 'owner', requestId,
|
|
680
|
+
...(group.caption ? { displayText: String(group.caption.text ?? '') } : {}) },
|
|
675
681
|
});
|
|
676
682
|
const accepted = this.options.config.interrupt
|
|
677
683
|
? ownerNotices.receivedInterrupting()
|
|
@@ -685,6 +691,8 @@ export class OwnerChannel {
|
|
|
685
691
|
const active = {
|
|
686
692
|
contact: sender.id, wireId: originWireId, requestId, outboundTail: receipt,
|
|
687
693
|
finalizing: false, updateCount: 0, updateDigests: new Set(), handledWireIds,
|
|
694
|
+
commentaryBuffer: '', commentaryCount: 0, commentaryKeys: new Set(),
|
|
695
|
+
commentaryDisabled: false,
|
|
688
696
|
};
|
|
689
697
|
this.activeRequests.set(requestId, active);
|
|
690
698
|
const cleanupDir = requestDir;
|
|
@@ -783,9 +791,9 @@ export class OwnerChannel {
|
|
|
783
791
|
// never prevent an authenticated owner from using the ordinary channel.
|
|
784
792
|
this.logError('owner conversation route update failed', error);
|
|
785
793
|
}
|
|
786
|
-
const text = String(message.text ?? '')
|
|
787
|
-
if (isOwnerCommandText(text)) {
|
|
788
|
-
await this.handleCommand(sender, text, wireId);
|
|
794
|
+
const text = String(message.text ?? '');
|
|
795
|
+
if (isOwnerCommandText(text.trim())) {
|
|
796
|
+
await this.handleCommand(sender, text.trim(), wireId);
|
|
789
797
|
return true;
|
|
790
798
|
}
|
|
791
799
|
const requestId = this.requestId(wireId);
|
|
@@ -797,7 +805,7 @@ export class OwnerChannel {
|
|
|
797
805
|
queued = await this.options.session.queuePrompt(this.ownerPrompt(sender, text, wireId, outbox), {
|
|
798
806
|
interrupt: this.options.config.interrupt,
|
|
799
807
|
...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
|
|
800
|
-
origin: { kind: 'owner', requestId },
|
|
808
|
+
origin: { kind: 'owner', requestId, displayText: text },
|
|
801
809
|
});
|
|
802
810
|
}
|
|
803
811
|
catch (error) {
|
|
@@ -822,6 +830,8 @@ export class OwnerChannel {
|
|
|
822
830
|
const active = {
|
|
823
831
|
contact: sender.id, wireId, requestId, outboundTail: receipt, finalizing: false,
|
|
824
832
|
updateCount: 0, updateDigests: new Set(), handledWireIds: [wireId],
|
|
833
|
+
commentaryBuffer: '', commentaryCount: 0, commentaryKeys: new Set(),
|
|
834
|
+
commentaryDisabled: false,
|
|
825
835
|
};
|
|
826
836
|
this.activeRequests.set(requestId, active);
|
|
827
837
|
const task = this.complete(active, outbox, queued, activityCursor)
|
|
@@ -1006,7 +1016,7 @@ export class OwnerChannel {
|
|
|
1006
1016
|
const contact = await this.routableContact(route);
|
|
1007
1017
|
const rejection = !this.attachmentRecovery.integrity()
|
|
1008
1018
|
? 'attachment recovery state is unavailable'
|
|
1009
|
-
:
|
|
1019
|
+
: validateAttachmentRelaySelection(group.files, this.attachmentConfig);
|
|
1010
1020
|
if (rejection)
|
|
1011
1021
|
throw new Error(rejection);
|
|
1012
1022
|
const transactionId = this.requestId(`managed-agent-attachment:${handledWireIds.slice().sort().join(':')}`);
|
|
@@ -1033,7 +1043,7 @@ export class OwnerChannel {
|
|
|
1033
1043
|
}
|
|
1034
1044
|
const order = new Map(group.files.map((file, index) => [file.wireId, index]));
|
|
1035
1045
|
retrieved.sort((a, b) => order.get(a.wireId) - order.get(b.wireId));
|
|
1036
|
-
const admitted = await admitAttachments(retrieved, requestDir, this.attachmentConfig);
|
|
1046
|
+
const admitted = await admitAttachments(retrieved, requestDir, this.attachmentConfig, { mimePolicy: 'report-only' });
|
|
1037
1047
|
const digest = createHash('sha256').update(`managed-agent-attachment\0${handledWireIds.slice().sort().join('\0')}`).digest('hex');
|
|
1038
1048
|
const sending = this.conversations.beginSend(route.contact, digest, Date.now(), 0, 'all');
|
|
1039
1049
|
try {
|
|
@@ -1196,6 +1206,95 @@ export class OwnerChannel {
|
|
|
1196
1206
|
let startedAt;
|
|
1197
1207
|
let phase = 'starting request';
|
|
1198
1208
|
let timer;
|
|
1209
|
+
const flushCommentary = () => {
|
|
1210
|
+
if (active.commentaryTimer)
|
|
1211
|
+
clearTimeout(active.commentaryTimer);
|
|
1212
|
+
active.commentaryTimer = undefined;
|
|
1213
|
+
const text = active.commentaryBuffer.trim();
|
|
1214
|
+
active.commentaryBuffer = '';
|
|
1215
|
+
if (!text || active.commentaryDisabled || active.finalizing)
|
|
1216
|
+
return;
|
|
1217
|
+
if (active.commentaryCount >= COMMENTARY_MAX_UPDATES) {
|
|
1218
|
+
active.commentaryDisabled = true;
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
let safe;
|
|
1222
|
+
try {
|
|
1223
|
+
safe = this.safeCommentary(text);
|
|
1224
|
+
}
|
|
1225
|
+
catch (error) {
|
|
1226
|
+
active.commentaryDisabled = true;
|
|
1227
|
+
this.logError('ACP commentary forwarding disabled for unsafe content', error);
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
const digest = createHash('sha256')
|
|
1231
|
+
.update(`owner-commentary\0${active.wireId}\0${safe}`).digest('hex');
|
|
1232
|
+
let sending;
|
|
1233
|
+
try {
|
|
1234
|
+
sending = this.conversations.beginSend(active.contact, digest, Date.now(), 0, 'all');
|
|
1235
|
+
}
|
|
1236
|
+
catch (error) {
|
|
1237
|
+
if (error instanceof DuplicateSendError)
|
|
1238
|
+
return;
|
|
1239
|
+
active.commentaryDisabled = true;
|
|
1240
|
+
this.logError('ACP commentary forwarding disabled by dedupe state', error);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
active.commentaryCount++;
|
|
1244
|
+
active.outboundTail = active.outboundTail
|
|
1245
|
+
.then(async () => {
|
|
1246
|
+
try {
|
|
1247
|
+
if (!this.isEffectiveOwner(active.contact))
|
|
1248
|
+
throw new Error('initiating owner is no longer authorized');
|
|
1249
|
+
await this.send(active.contact, safe, active.wireId);
|
|
1250
|
+
}
|
|
1251
|
+
catch {
|
|
1252
|
+
this.conversations.finishSend(sending.id, 'uncertain');
|
|
1253
|
+
throw new Error('ACP commentary delivery outcome is uncertain');
|
|
1254
|
+
}
|
|
1255
|
+
this.conversations.finishSend(sending.id, 'delivered');
|
|
1256
|
+
})
|
|
1257
|
+
.catch(error => this.logError('ACP commentary delivery failed', error));
|
|
1258
|
+
};
|
|
1259
|
+
const acceptCommentary = (event) => {
|
|
1260
|
+
if (active.commentaryDisabled || active.finalizing || event.turnId !== queued.promptId
|
|
1261
|
+
|| event.kind !== 'agent_text' || event.messagePhase !== 'commentary'
|
|
1262
|
+
|| event.replayed || event.origin?.kind !== 'owner'
|
|
1263
|
+
|| event.origin.requestId !== active.requestId
|
|
1264
|
+
|| typeof event.messageId !== 'string' || !event.messageId
|
|
1265
|
+
|| typeof event.text !== 'string' || !event.text)
|
|
1266
|
+
return;
|
|
1267
|
+
const key = createHash('sha256')
|
|
1268
|
+
.update(`${event.messageId}\0${event.text}`).digest('hex');
|
|
1269
|
+
if (active.commentaryKeys.has(key))
|
|
1270
|
+
return;
|
|
1271
|
+
active.commentaryKeys.add(key);
|
|
1272
|
+
if (active.commentaryKeys.size > COMMENTARY_DEDUPE_LIMIT)
|
|
1273
|
+
active.commentaryKeys.delete(active.commentaryKeys.values().next().value);
|
|
1274
|
+
let chars = Array.from(active.commentaryBuffer).length;
|
|
1275
|
+
let bytes = Buffer.byteLength(active.commentaryBuffer);
|
|
1276
|
+
for (const point of event.text) {
|
|
1277
|
+
const pointBytes = Buffer.byteLength(point);
|
|
1278
|
+
if (chars >= COMMENTARY_MAX_CHARS || bytes + pointBytes > COMMENTARY_MAX_BYTES) {
|
|
1279
|
+
flushCommentary();
|
|
1280
|
+
if (active.commentaryDisabled)
|
|
1281
|
+
return;
|
|
1282
|
+
chars = 0;
|
|
1283
|
+
bytes = 0;
|
|
1284
|
+
}
|
|
1285
|
+
active.commentaryBuffer += point;
|
|
1286
|
+
chars++;
|
|
1287
|
+
bytes += pointBytes;
|
|
1288
|
+
}
|
|
1289
|
+
if (/\n\s*\n$/u.test(active.commentaryBuffer)) {
|
|
1290
|
+
flushCommentary();
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
if (!active.commentaryTimer) {
|
|
1294
|
+
active.commentaryTimer = setTimeout(flushCommentary, COMMENTARY_FLUSH_MS);
|
|
1295
|
+
active.commentaryTimer.unref?.();
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1199
1298
|
const reportProgress = () => {
|
|
1200
1299
|
const events = this.options.session.eventsSince(lastSeq);
|
|
1201
1300
|
lastSeq = Math.max(lastSeq, this.latestEventSeq(events));
|
|
@@ -1234,19 +1333,28 @@ export class OwnerChannel {
|
|
|
1234
1333
|
timer = setInterval(reportProgress, progressMs);
|
|
1235
1334
|
timer.unref();
|
|
1236
1335
|
};
|
|
1237
|
-
const unsubscribe =
|
|
1238
|
-
? this.options.session.subscribe(event =>
|
|
1239
|
-
|
|
1336
|
+
const unsubscribe = typeof this.options.session.subscribe === 'function'
|
|
1337
|
+
? this.options.session.subscribe(event => {
|
|
1338
|
+
startProgress(event);
|
|
1339
|
+
// Automatic commentary is an ACP phase extension. Other backends and
|
|
1340
|
+
// older adapters retain their established final-only behavior.
|
|
1341
|
+
if (this.options.session.backend === 'acp')
|
|
1342
|
+
acceptCommentary(event);
|
|
1343
|
+
})
|
|
1344
|
+
: () => undefined;
|
|
1240
1345
|
startProgress();
|
|
1241
1346
|
let result;
|
|
1242
1347
|
try {
|
|
1243
1348
|
result = await queued.completion;
|
|
1244
1349
|
}
|
|
1245
1350
|
finally {
|
|
1246
|
-
unsubscribe
|
|
1351
|
+
unsubscribe();
|
|
1247
1352
|
if (timer)
|
|
1248
1353
|
clearInterval(timer);
|
|
1354
|
+
if (active.commentaryTimer)
|
|
1355
|
+
clearTimeout(active.commentaryTimer);
|
|
1249
1356
|
}
|
|
1357
|
+
flushCommentary();
|
|
1250
1358
|
active.finalizing = true;
|
|
1251
1359
|
await active.outboundTail;
|
|
1252
1360
|
const output = result.output?.trim();
|
|
@@ -1267,6 +1375,20 @@ export class OwnerChannel {
|
|
|
1267
1375
|
for (const wire of active.handledWireIds)
|
|
1268
1376
|
this.state.remember(wire);
|
|
1269
1377
|
}
|
|
1378
|
+
/** Model-authored commentary only; raw protocol/tool data never reaches here. */
|
|
1379
|
+
safeCommentary(value) {
|
|
1380
|
+
const message = value.trim().normalize('NFC');
|
|
1381
|
+
if (!message)
|
|
1382
|
+
throw new Error('commentary is empty');
|
|
1383
|
+
if (Array.from(message).length > COMMENTARY_MAX_CHARS
|
|
1384
|
+
|| Buffer.byteLength(message) > COMMENTARY_MAX_BYTES)
|
|
1385
|
+
throw new Error('commentary batch exceeds its bounded size');
|
|
1386
|
+
if (/\u0000|[\u202a-\u202e\u2066-\u2069]/u.test(message))
|
|
1387
|
+
throw new Error('commentary contains unsafe control characters');
|
|
1388
|
+
if (/-----BEGIN [A-Z ]*PRIVATE KEY-----|(?:api[_ -]?key|access[_ -]?token|authorization|password|secret)\s*[:=]|(?:chain of thought|private reasoning|internal reasoning)|^(?:stdout|stderr|tool (?:output|result)|command):/imu.test(message))
|
|
1389
|
+
throw new Error('commentary appears to contain secret, reasoning, or raw tool content');
|
|
1390
|
+
return message;
|
|
1391
|
+
}
|
|
1270
1392
|
ownerAttachmentPrompt(sender, wireId, requestId, outbox, files, caption) {
|
|
1271
1393
|
const lines = [
|
|
1272
1394
|
'[fleet-owner]',
|
|
@@ -43,7 +43,7 @@ export class OwnerChannelState {
|
|
|
43
43
|
}
|
|
44
44
|
const CONVERSATION_LIMIT = 64;
|
|
45
45
|
const WIRE_ROUTE_LIMIT = 512;
|
|
46
|
-
const PROACTIVE_SEND_LIMIT =
|
|
46
|
+
const PROACTIVE_SEND_LIMIT = 2_048;
|
|
47
47
|
const PROACTIVE_MIN_INTERVAL_MS = 30_000;
|
|
48
48
|
const HEX_64_LOWER = /^[a-f0-9]{64}$/;
|
|
49
49
|
const CID = /^[A-Fa-f0-9]{64}$/;
|
|
@@ -158,7 +158,10 @@ export class OwnerConversationState {
|
|
|
158
158
|
if (candidates.length) {
|
|
159
159
|
if (candidates[1]?.lastInboundAt === candidates[0].lastInboundAt)
|
|
160
160
|
throw new Error('proactive owner route is ambiguous');
|
|
161
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
contact: candidates[0].contact, basis: 'last-inbound',
|
|
163
|
+
replyToWireId: candidates[0].lastInboundWireId,
|
|
164
|
+
};
|
|
162
165
|
}
|
|
163
166
|
if (canonical.size === 1)
|
|
164
167
|
return { contact: [...effective][0], basis: 'sole-owner' };
|
|
@@ -180,7 +183,7 @@ export class OwnerConversationState {
|
|
|
180
183
|
const recent = this.sends.filter(send => canonicalCid(send.contact) === canonical).slice(-128);
|
|
181
184
|
// 'all' scope serves wire-keyed idempotency: a crash replay must not
|
|
182
185
|
// deliver the same wire to a second owner after the route moved.
|
|
183
|
-
const scope = dedupe === 'all' ? this.sends
|
|
186
|
+
const scope = dedupe === 'all' ? this.sends : recent;
|
|
184
187
|
if (scope.some(send => send.digest === digest))
|
|
185
188
|
throw new DuplicateSendError('duplicate proactive owner message refused');
|
|
186
189
|
const last = recent.at(-1);
|
package/dist/permissions.d.ts
CHANGED
|
@@ -62,6 +62,11 @@ export interface PermissionConflict {
|
|
|
62
62
|
/** The role-named line commands print. */
|
|
63
63
|
warning: string;
|
|
64
64
|
}
|
|
65
|
+
/** Resolve the effective portable policy after harness-native overrides win. */
|
|
66
|
+
export declare function effectivePermissionMode(role: ResolvedRole): {
|
|
67
|
+
fleetMode: import('./config.js').FleetPermissionMode;
|
|
68
|
+
nativeMode: string;
|
|
69
|
+
};
|
|
65
70
|
/** Resolve one role's permissions through its adapter. Never throws. */
|
|
66
71
|
export declare function analyzeRolePermissions(role: ResolvedRole): RolePermissionAnalysis;
|
|
67
72
|
/** Every line a command should show for a role: translation, conflicts, floor. */
|
package/dist/permissions.js
CHANGED
|
@@ -23,6 +23,13 @@ export function checkUnattendedFloor(granted, required = UNATTENDED_FLOOR) {
|
|
|
23
23
|
const missing = required.filter(c => !granted.includes(c));
|
|
24
24
|
return { meets: missing.length === 0, missing };
|
|
25
25
|
}
|
|
26
|
+
/** Resolve the effective portable policy after harness-native overrides win. */
|
|
27
|
+
export function effectivePermissionMode(role) {
|
|
28
|
+
const adapter = getAdapter(role.harness);
|
|
29
|
+
if (!adapter.effectivePermissionMode)
|
|
30
|
+
throw new Error(`harness '${role.harness}' cannot report an effective ask|auto|allow permission mode`);
|
|
31
|
+
return adapter.effectivePermissionMode(role);
|
|
32
|
+
}
|
|
26
33
|
/**
|
|
27
34
|
* Find native settings that contradict the neutral block. Only fires when the
|
|
28
35
|
* operator wrote BOTH — a role that states its intent once, neutrally or
|
package/dist/runner.js
CHANGED
|
@@ -23,6 +23,7 @@ import { acquireOwnerBinderLease, OwnerBinderHandoffTimeoutError, } from './owne
|
|
|
23
23
|
import { RoleTurnArbiter } from './session/arbiter.js';
|
|
24
24
|
import { ScheduledLoopManager, } from './loops/manager.js';
|
|
25
25
|
import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, inheritCallerSpawnDefaults, } from './fleet-proxy.js';
|
|
26
|
+
import { effectivePermissionMode } from './permissions.js';
|
|
26
27
|
const defaultDeps = () => ({
|
|
27
28
|
tmux: new Tmux(),
|
|
28
29
|
exec: realExec,
|
|
@@ -514,6 +515,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
514
515
|
mode,
|
|
515
516
|
permissions: perms,
|
|
516
517
|
modeId: adapter.acpPermissionModeId?.(role),
|
|
518
|
+
permissionMode: effectivePermissionMode(role),
|
|
517
519
|
log: deps.log,
|
|
518
520
|
});
|
|
519
521
|
pid = acpSession.pid;
|
package/dist/session/acp.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import * as acp from '@agentclientprotocol/sdk';
|
|
1
2
|
import type { CommonPermissions } from '../config.js';
|
|
2
|
-
import
|
|
3
|
+
import { ConversationEventStore } from './conversation-store.js';
|
|
4
|
+
import type { ConversationSnapshot, PromptOrigin, PromptReceipt, SubmitPromptCommand } from './conversation-types.js';
|
|
5
|
+
import type { ConversationHandlePage, ExitRecord, QueuedPrompt, SessionEvent, RuntimeSelectorMetadata, SessionHandle, SessionSnapshot, SubmitPromptOptions, TurnCancellationSource, TurnOutcome, TurnResult } from './types.js';
|
|
6
|
+
/** Server-generated typed provenance followed by the exact human-authored body. */
|
|
7
|
+
export declare function promptContentBlocks(text: string, origin?: PromptOrigin): acp.ContentBlock[];
|
|
8
|
+
export declare function runtimeSelector(options: acp.SessionConfigOption[] | null | undefined, category: string): RuntimeSelectorMetadata | undefined;
|
|
3
9
|
export interface AcpSessionOptions {
|
|
4
10
|
name: string;
|
|
5
11
|
argv: string[];
|
|
@@ -10,9 +16,15 @@ export interface AcpSessionOptions {
|
|
|
10
16
|
permissions: CommonPermissions;
|
|
11
17
|
/** Native permission-mode id to request via session/set_mode; undefined keeps the agent default. */
|
|
12
18
|
modeId?: string;
|
|
19
|
+
/** Adapter-resolved live permission policy; separate from ACP agent-specific session modes. */
|
|
20
|
+
permissionMode?: NonNullable<SessionSnapshot['permissionMode']>;
|
|
13
21
|
log(line: string): void;
|
|
14
22
|
/** Test seam for the cancel-escalation grace period; production uses the default. */
|
|
15
23
|
cancelGraceMs?: number;
|
|
24
|
+
/** How long a pending permission may wait for a human before it expires. */
|
|
25
|
+
permissionTimeoutMs?: number;
|
|
26
|
+
/** Grace after the last controller detaches before the unattended policy applies. */
|
|
27
|
+
controllerGraceMs?: number;
|
|
16
28
|
}
|
|
17
29
|
/**
|
|
18
30
|
* Classify an ACP `stopReason` into a terminal outcome. A refusal and a
|
|
@@ -30,6 +42,11 @@ export declare class AcpSession implements SessionHandle {
|
|
|
30
42
|
readonly pid: number;
|
|
31
43
|
private readonly child;
|
|
32
44
|
private readonly events;
|
|
45
|
+
private readonly conversation;
|
|
46
|
+
/** New on every runner start; permission/turn IDs from prior generations are stale. */
|
|
47
|
+
private readonly sessionGeneration;
|
|
48
|
+
/** True while `session/load` replays history as ordinary updates. */
|
|
49
|
+
private replaying;
|
|
33
50
|
private readonly sessionFile;
|
|
34
51
|
private readonly pendingPermissions;
|
|
35
52
|
private connection;
|
|
@@ -41,11 +58,22 @@ export declare class AcpSession implements SessionHandle {
|
|
|
41
58
|
private exit;
|
|
42
59
|
private steeringSupported;
|
|
43
60
|
private capabilities?;
|
|
61
|
+
private runtimeModel?;
|
|
62
|
+
private reasoningEffort?;
|
|
44
63
|
private controllerCount;
|
|
64
|
+
/** Armed when the last controller detaches; unattended policy applies on fire. */
|
|
65
|
+
private controllerGrace?;
|
|
45
66
|
private cancelEscalation?;
|
|
46
67
|
private activeTurn?;
|
|
47
68
|
private constructor();
|
|
48
69
|
static start(options: AcpSessionOptions): Promise<AcpSession>;
|
|
70
|
+
/**
|
|
71
|
+
* Honest restart recovery (spec §5.3): a prompt that was admitted but never
|
|
72
|
+
* started is safe to dispatch again; a turn that had already started may
|
|
73
|
+
* have caused side effects, so it is closed as `unknown_after_restart` —
|
|
74
|
+
* never silently replayed.
|
|
75
|
+
*/
|
|
76
|
+
private recoverOpenPrompts;
|
|
49
77
|
isAlive(): boolean;
|
|
50
78
|
snapshot(): SessionSnapshot;
|
|
51
79
|
/**
|
|
@@ -54,16 +82,40 @@ export declare class AcpSession implements SessionHandle {
|
|
|
54
82
|
* for it is what turned a busy agent into a timeout and then into "dead".
|
|
55
83
|
*/
|
|
56
84
|
queuePrompt(text: string, options?: SubmitPromptOptions): Promise<QueuedPrompt>;
|
|
85
|
+
/**
|
|
86
|
+
* Durably record a prompt admission BEFORE acceptance is returned. Browser
|
|
87
|
+
* admissions are transactional — a prompt the ledger cannot hold is refused,
|
|
88
|
+
* because an acknowledged-then-lost prompt is worse than an error. Every
|
|
89
|
+
* other source degrades to best-effort so the agent keeps working (§5.3).
|
|
90
|
+
*/
|
|
91
|
+
private admitToLedger;
|
|
92
|
+
/** Idempotent browser prompt admission (control v3 `submit_prompt_v2`). */
|
|
93
|
+
submitPromptBrowser(command: SubmitPromptCommand): Promise<PromptReceipt>;
|
|
57
94
|
submitPrompt(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
|
|
58
95
|
interrupt(source?: TurnCancellationSource): Promise<void>;
|
|
59
96
|
private cancelActive;
|
|
60
97
|
respondPermission(permissionId: string, optionId: string): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* A v2 decision binds to the session generation it was shown under. A stale
|
|
100
|
+
* generation, an already-settled request, or an unknown option are all the
|
|
101
|
+
* same answer: someone else's decision (or a restart) got there first.
|
|
102
|
+
*/
|
|
103
|
+
respondPermissionV2(permissionId: string, optionId: string, sessionGeneration: string): 'accepted' | 'stale';
|
|
61
104
|
eventsSince(seq: number): SessionEvent[];
|
|
62
105
|
subscribe(listener: (event: SessionEvent) => void): () => void;
|
|
63
106
|
setControllerAttached(attached: boolean): void;
|
|
107
|
+
private armControllerGrace;
|
|
108
|
+
/**
|
|
109
|
+
* Settle one pending request without a human decision — unattended policy,
|
|
110
|
+
* expiry, or cancellation — and leave the same durable evidence a manual
|
|
111
|
+
* decision would. A denial selects the agent's own one-shot reject option;
|
|
112
|
+
* everything else resolves as cancelled toward the agent.
|
|
113
|
+
*/
|
|
114
|
+
private settlePendingAutomatically;
|
|
64
115
|
exitResult(): ExitRecord | null;
|
|
65
116
|
close(): Promise<void>;
|
|
66
117
|
private initialize;
|
|
118
|
+
private captureRuntimeMetadata;
|
|
67
119
|
private runPrompt;
|
|
68
120
|
private steerPrompt;
|
|
69
121
|
private requestPermission;
|
|
@@ -75,5 +127,18 @@ export declare class AcpSession implements SessionHandle {
|
|
|
75
127
|
private settleAutomatically;
|
|
76
128
|
private withinAutomaticBoundary;
|
|
77
129
|
private recordUpdate;
|
|
130
|
+
/**
|
|
131
|
+
* Codex ACP's phase extension is the only currently supported visibility
|
|
132
|
+
* signal. Never infer commentary from text, message order, or unknown meta.
|
|
133
|
+
*/
|
|
134
|
+
private codexMessagePhase;
|
|
135
|
+
/** Normalize every ACP update losslessly into the durable ledger. */
|
|
136
|
+
private recordConversationUpdate;
|
|
137
|
+
conversationPage(request?: {
|
|
138
|
+
after?: string;
|
|
139
|
+
limit?: number;
|
|
140
|
+
}): ConversationHandlePage;
|
|
141
|
+
conversationSnapshot(): ConversationSnapshot;
|
|
142
|
+
subscribeConversation(listener: Parameters<ConversationEventStore['subscribe']>[0]): () => void;
|
|
78
143
|
private fail;
|
|
79
144
|
}
|