@ours.network/fleet 1.1.5 → 1.2.0-nightly.2
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 +88 -1
- package/dist/build-info.json +5 -5
- package/dist/cli.js +12 -6
- package/dist/client-profile.d.ts +17 -0
- package/dist/client-profile.js +115 -0
- package/dist/creation.js +10 -7
- package/dist/daemon-recovery.d.ts +2 -0
- package/dist/daemon-recovery.js +53 -2
- package/dist/doctor.js +41 -4
- package/dist/harness/codex.js +4 -0
- package/dist/init-wizard.d.ts +11 -6
- package/dist/init-wizard.js +33 -0
- package/dist/monitor.d.ts +34 -3
- package/dist/monitor.js +174 -74
- package/dist/owner-channel/channel.js +4 -2
- package/dist/owner-channel/ours-client.d.ts +12 -5
- package/dist/owner-channel/ours-client.js +48 -12
- package/dist/runner.js +21 -7
- package/dist/session/codex-app-server.js +31 -5
- package/dist/supervisor/launchd.js +8 -1
- package/package.json +5 -4
package/dist/runner.js
CHANGED
|
@@ -908,9 +908,11 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
908
908
|
void recoveryController.recover(observation.generation).catch(error => deps.log(`[${name}] daemon recovery controller failed: ${error?.name ?? 'Error'}`));
|
|
909
909
|
}
|
|
910
910
|
}
|
|
911
|
-
if (
|
|
912
|
-
|
|
913
|
-
|
|
911
|
+
if (deps.shouldStop?.()) {
|
|
912
|
+
if (temp) {
|
|
913
|
+
retirementReason = requestedTempStopReason(dir) ?? 'supervisor-signal';
|
|
914
|
+
deps.log(`[${name}] temporary supervisor retirement requested (${retirementReason})`);
|
|
915
|
+
}
|
|
914
916
|
await sessionHandle.close();
|
|
915
917
|
sessionClosed = true;
|
|
916
918
|
break;
|
|
@@ -1002,7 +1004,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1002
1004
|
rotated = true;
|
|
1003
1005
|
deps.log(`[${name}] ${why} -> rotated session-id; next start is FRESH`);
|
|
1004
1006
|
};
|
|
1005
|
-
if (
|
|
1007
|
+
if (!temp && deps.shouldStop?.())
|
|
1008
|
+
deps.log(`[${name}] supervisor stop requested -> next start RESUMES context`);
|
|
1009
|
+
else if (exitRecord.detail.includes(ACP_CANCEL_DEADLINE_EXCEEDED)
|
|
1006
1010
|
|| exitRecord.detail.includes(CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED))
|
|
1007
1011
|
// This is a deliberate adapter reclamation, not evidence that resume state
|
|
1008
1012
|
// is poisoned. Preserve the context even when the resumed generation hits
|
|
@@ -1049,7 +1053,10 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1049
1053
|
const deps = { ...defaultDeps(), ...partialDeps };
|
|
1050
1054
|
const dir = agentDir(name);
|
|
1051
1055
|
mkdirSync(dir, { recursive: true });
|
|
1052
|
-
|
|
1056
|
+
let stopping = false;
|
|
1057
|
+
const requestStop = () => { stopping = true; };
|
|
1058
|
+
const shouldStop = () => stopping || (partialDeps.shouldStop?.() ?? false);
|
|
1059
|
+
deps.shouldStop = shouldStop;
|
|
1053
1060
|
const stamp = () => new Date(deps.now()).toISOString();
|
|
1054
1061
|
// Record how the PREVIOUS supervisor process ended before doing anything
|
|
1055
1062
|
// else. An external kill writes nothing itself, and the next ledger write is
|
|
@@ -1071,6 +1078,9 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1071
1078
|
deps.log(`[${name}] previous supervisor run (started ${termination.runStartedAt}) `
|
|
1072
1079
|
+ `ended abruptly: ${termination.detail}; abrupt terminations recorded: ${abrupt}`);
|
|
1073
1080
|
}
|
|
1081
|
+
// Service-manager stops must run the same cleanup as a completed session.
|
|
1082
|
+
process.on('SIGTERM', requestStop);
|
|
1083
|
+
process.on('SIGINT', requestStop);
|
|
1074
1084
|
try {
|
|
1075
1085
|
while (!shouldStop()) {
|
|
1076
1086
|
let ledger = readRestartLedger(dir);
|
|
@@ -1125,6 +1135,8 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1125
1135
|
mode: 'fresh',
|
|
1126
1136
|
};
|
|
1127
1137
|
}
|
|
1138
|
+
if (stopping)
|
|
1139
|
+
break;
|
|
1128
1140
|
// Re-read: the attempt itself may have taken minutes, and an operator may
|
|
1129
1141
|
// have reset the ledger meanwhile.
|
|
1130
1142
|
ledger = readRestartLedger(dir);
|
|
@@ -1189,8 +1201,10 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1189
1201
|
return readRestartLedger(dir);
|
|
1190
1202
|
}
|
|
1191
1203
|
finally {
|
|
1192
|
-
|
|
1193
|
-
|
|
1204
|
+
process.off('SIGTERM', requestStop);
|
|
1205
|
+
process.off('SIGINT', requestStop);
|
|
1206
|
+
// An orderly shutdown clears the marker; an unhandled signal or OOM-kill
|
|
1207
|
+
// leaves it so the successor can identify an abrupt termination.
|
|
1194
1208
|
releaseSupervisorRun(dir);
|
|
1195
1209
|
}
|
|
1196
1210
|
}
|
|
@@ -879,6 +879,21 @@ export class CodexAppServerSession {
|
|
|
879
879
|
this.transport.respond(id, { currentTimeAt: Math.floor(Date.now() / 1_000) });
|
|
880
880
|
return;
|
|
881
881
|
}
|
|
882
|
+
if (method === 'mcpServer/elicitation/request') {
|
|
883
|
+
const schema = params.requestedSchema;
|
|
884
|
+
// The permission UI can answer a confirmation, but cannot collect form
|
|
885
|
+
// fields or perform URL-based verification on the user's behalf.
|
|
886
|
+
if (params.mode !== 'form' || !isObject(schema) || schema.type !== 'object'
|
|
887
|
+
|| !isObject(schema.properties) || Object.keys(schema.properties).length
|
|
888
|
+
|| (schema.required !== undefined
|
|
889
|
+
&& (!Array.isArray(schema.required) || schema.required.length))) {
|
|
890
|
+
this.options.log(`[${this.options.name}] codex app-server: unsupported MCP elicitation form`);
|
|
891
|
+
this.transport.respond(id, { action: 'decline', content: null, _meta: null });
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
this.requestPermission(method, id, params);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
882
897
|
if (method === 'item/tool/requestUserInput') {
|
|
883
898
|
this.options.log(`[${this.options.name}] codex app-server: requestUserInput has no interactive Fleet mapping; returning no answers`);
|
|
884
899
|
this.transport.respond(id, { answers: {} });
|
|
@@ -896,9 +911,11 @@ export class CodexAppServerSession {
|
|
|
896
911
|
{ optionId: 'allow_session', name: 'Allow for session', kind: 'allow_always', native: 'acceptForSession' },
|
|
897
912
|
{ optionId: 'deny_once', name: 'Deny', kind: 'reject_once', native: 'decline' },
|
|
898
913
|
{ optionId: 'cancel', name: 'Cancel', kind: 'reject_once', native: 'cancel' },
|
|
899
|
-
].filter(option => method === '
|
|
900
|
-
|
|
901
|
-
|
|
914
|
+
].filter(option => method === 'mcpServer/elicitation/request'
|
|
915
|
+
? option.optionId !== 'allow_session'
|
|
916
|
+
: method === 'item/fileChange/requestApproval'
|
|
917
|
+
|| method === 'item/permissions/requestApproval'
|
|
918
|
+
|| !available.length || available.includes(option.native))
|
|
902
919
|
.map(({ native: _native, ...option }) => option);
|
|
903
920
|
const pending = {
|
|
904
921
|
requestId, method, toolCallId, options,
|
|
@@ -909,7 +926,8 @@ export class CodexAppServerSession {
|
|
|
909
926
|
// `approval=allow` covers native operations inside the declared boundary;
|
|
910
927
|
// it must never silently widen filesystem or network authority.
|
|
911
928
|
if (this.options.permissions.approval === 'allow'
|
|
912
|
-
&& method !== 'item/permissions/requestApproval'
|
|
929
|
+
&& method !== 'item/permissions/requestApproval'
|
|
930
|
+
&& method !== 'mcpServer/elicitation/request') {
|
|
913
931
|
this.transport.respond(requestId, this.permissionResponse(pending, 'accept'));
|
|
914
932
|
this.resolvePermission(permissionId, pending, 'allowed', 'automatic', 'allow_once', 'permissions.approval=allow', 'the native request is inside the configured Codex boundary');
|
|
915
933
|
return;
|
|
@@ -925,7 +943,9 @@ export class CodexAppServerSession {
|
|
|
925
943
|
this.readiness = 'awaiting_permission';
|
|
926
944
|
const timeoutMs = this.options.permissionTimeoutMs ?? PERMISSION_TIMEOUT_MS;
|
|
927
945
|
const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
928
|
-
const title =
|
|
946
|
+
const title = (method === 'mcpServer/elicitation/request'
|
|
947
|
+
? `${string(params.serverName) ?? 'MCP'}: ${string(params.message) ?? 'Confirmation requested'}`
|
|
948
|
+
: undefined) ?? string(params.command) ?? string(params.reason)
|
|
929
949
|
?? (method === 'item/fileChange/requestApproval' ? 'Apply file changes'
|
|
930
950
|
: method === 'item/permissions/requestApproval' ? 'Grant additional permissions'
|
|
931
951
|
: 'Run command');
|
|
@@ -993,6 +1013,12 @@ export class CodexAppServerSession {
|
|
|
993
1013
|
return !turnId || !this.activeTurn.nativeTurnId || turnId === this.activeTurn.nativeTurnId;
|
|
994
1014
|
}
|
|
995
1015
|
permissionResponse(pending, decision) {
|
|
1016
|
+
if (pending.method === 'mcpServer/elicitation/request')
|
|
1017
|
+
return {
|
|
1018
|
+
action: decision === 'acceptForSession' ? 'accept' : decision,
|
|
1019
|
+
content: decision.startsWith('accept') ? {} : null,
|
|
1020
|
+
_meta: null,
|
|
1021
|
+
};
|
|
996
1022
|
if (pending.method !== 'item/permissions/requestApproval')
|
|
997
1023
|
return { decision };
|
|
998
1024
|
const requested = pending.requestedPermissions ?? {};
|
|
@@ -14,8 +14,15 @@ const LAST_EXIT_RE = /^\s*last exit (?:code|status|reason)\s*=\s*(.+?)\s*$/mi;
|
|
|
14
14
|
const STATE_RE = /^\s*state\s*=\s*(.+?)\s*$/m;
|
|
15
15
|
const agentsDir = () => join(home(), 'Library', 'LaunchAgents');
|
|
16
16
|
const plistPath = (name) => join(agentsDir(), `${labelFor(name)}.plist`);
|
|
17
|
+
const xml = (value) => value
|
|
18
|
+
.replaceAll('&', '&')
|
|
19
|
+
.replaceAll('<', '<')
|
|
20
|
+
.replaceAll('>', '>')
|
|
21
|
+
.replaceAll('"', '"')
|
|
22
|
+
.replaceAll("'", ''');
|
|
17
23
|
function plist(name, binPath) {
|
|
18
24
|
const log = join(logsRoot(), `${name}.log`);
|
|
25
|
+
const config = process.env.OURS_CONFIG;
|
|
19
26
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
20
27
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
21
28
|
<plist version="1.0">
|
|
@@ -23,7 +30,7 @@ function plist(name, binPath) {
|
|
|
23
30
|
<key>Label</key><string>${labelFor(name)}</string>
|
|
24
31
|
<key>ProgramArguments</key>
|
|
25
32
|
<array><string>${binPath}</string><string>_run</string><string>${name}</string></array>
|
|
26
|
-
<!-- The runner owns the child-session restart loop. launchd must only
|
|
33
|
+
${config ? ` <key>EnvironmentVariables</key><dict><key>OURS_CONFIG</key><string>${xml(config)}</string></dict>\n` : ''} <!-- The runner owns the child-session restart loop. launchd must only
|
|
27
34
|
recover the runner PROCESS crashing: a bare KeepAlive would resume the
|
|
28
35
|
uncounted relaunch loop and restart a deliberately held-down agent. -->
|
|
29
36
|
<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-nightly.2",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|
|
@@ -34,14 +34,15 @@
|
|
|
34
34
|
"test:e2e": "npm run build && playwright test",
|
|
35
35
|
"dev:web": "vite",
|
|
36
36
|
"prepack": "npm run build",
|
|
37
|
-
"prepublishOnly": "npm run build && npm test"
|
|
37
|
+
"prepublishOnly": "npm run build && npm test",
|
|
38
|
+
"test:v1-recovery": "node test/fleet-v1-recovery.integration.mjs retry && node test/fleet-v1-recovery.integration.mjs terminal-after-failure && node test/fleet-v1-recovery.integration.mjs terminal-release-failure"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"@agentclientprotocol/sdk": "^1.3.0",
|
|
41
42
|
"@fastify/static": "^10.1.2",
|
|
42
43
|
"@fastify/websocket": "^11.2.0",
|
|
43
|
-
"@ours.network/cli": "
|
|
44
|
-
"@ours.network/sdk": "3.
|
|
44
|
+
"@ours.network/cli": "2.8.1-nightly.2",
|
|
45
|
+
"@ours.network/sdk": "3.8.1-nightly.4",
|
|
45
46
|
"commander": "^12.1.0",
|
|
46
47
|
"fastify": "^5.4.0",
|
|
47
48
|
"react": "^19.1.1",
|