@ours.network/fleet 1.1.4 → 1.2.0-nightly.1
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 +137 -1
- package/dist/briefing.js +4 -3
- package/dist/build-info.json +5 -5
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +13 -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/docs.d.ts +1 -1
- package/dist/docs.js +23 -2
- package/dist/doctor.js +62 -6
- package/dist/harness/acp-mcp.d.ts +14 -0
- package/dist/harness/acp-mcp.js +68 -0
- package/dist/harness/claude-code.js +1 -69
- package/dist/harness/codex.js +4 -0
- package/dist/harness/hermes-compatibility.d.ts +24 -0
- package/dist/harness/hermes-compatibility.js +191 -0
- package/dist/harness/hermes-config.d.ts +12 -0
- package/dist/harness/hermes-config.js +367 -0
- package/dist/harness/hermes-permissions.d.ts +4 -0
- package/dist/harness/hermes-permissions.js +36 -0
- package/dist/harness/hermes-session.d.ts +24 -0
- package/dist/harness/hermes-session.js +85 -0
- package/dist/harness/hermes-startup.d.ts +3 -0
- package/dist/harness/hermes-startup.js +21 -0
- package/dist/harness/hermes.d.ts +5 -0
- package/dist/harness/hermes.js +62 -0
- package/dist/harness/registry.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -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/acp.d.ts +15 -0
- package/dist/session/acp.js +22 -7
- package/dist/session/codex-app-server.js +31 -5
- package/dist/spawn.d.ts +1 -0
- package/dist/spawn.js +1 -0
- package/dist/supervisor/launchd.js +8 -1
- package/examples/fleet/brains/hermes.yaml +5 -0
- package/package.json +5 -4
package/dist/session/acp.js
CHANGED
|
@@ -332,7 +332,7 @@ export class AcpSession {
|
|
|
332
332
|
// Obsolete ours-mcp lifecycle flags are presence-sensitive. The shared
|
|
333
333
|
// daemon remains operator-owned; managed ACP children are clients only.
|
|
334
334
|
const childEnv = {
|
|
335
|
-
...process.env,
|
|
335
|
+
...(options.inheritEnvironment === false ? {} : process.env),
|
|
336
336
|
...options.env,
|
|
337
337
|
};
|
|
338
338
|
if (options.scrubObsoleteOursAutostart)
|
|
@@ -1067,6 +1067,7 @@ export class AcpSession {
|
|
|
1067
1067
|
? readFileSync(this.sessionFile, 'utf8').trim()
|
|
1068
1068
|
: '';
|
|
1069
1069
|
let advertisedConfigOptions;
|
|
1070
|
+
let advertisedModes;
|
|
1070
1071
|
let advertisedModelId;
|
|
1071
1072
|
if (persisted && this.agentCapabilities?.sessionCapabilities?.resume != null) {
|
|
1072
1073
|
const resumed = await this.connection.agent.request(acp.methods.agent.session.resume, {
|
|
@@ -1075,6 +1076,7 @@ export class AcpSession {
|
|
|
1075
1076
|
mcpServers: this.declaredMcpServers(),
|
|
1076
1077
|
});
|
|
1077
1078
|
advertisedConfigOptions = resumed.configOptions;
|
|
1079
|
+
advertisedModes = resumed.modes;
|
|
1078
1080
|
advertisedModelId = resumed.models?.currentModelId;
|
|
1079
1081
|
this.captureRuntimeMetadata(advertisedConfigOptions, advertisedModelId);
|
|
1080
1082
|
this.sessionId = persisted;
|
|
@@ -1090,6 +1092,7 @@ export class AcpSession {
|
|
|
1090
1092
|
mcpServers: this.declaredMcpServers(),
|
|
1091
1093
|
});
|
|
1092
1094
|
advertisedConfigOptions = loaded.configOptions;
|
|
1095
|
+
advertisedModes = loaded.modes;
|
|
1093
1096
|
advertisedModelId = loaded.models?.currentModelId;
|
|
1094
1097
|
this.captureRuntimeMetadata(advertisedConfigOptions, advertisedModelId);
|
|
1095
1098
|
}
|
|
@@ -1105,6 +1108,8 @@ export class AcpSession {
|
|
|
1105
1108
|
...(this.options.sessionMeta ? { _meta: this.options.sessionMeta } : {}),
|
|
1106
1109
|
});
|
|
1107
1110
|
this.sessionId = created.sessionId;
|
|
1111
|
+
await this.options.validateStartupResponse?.(initialized, created);
|
|
1112
|
+
advertisedModes = created.modes;
|
|
1108
1113
|
advertisedConfigOptions = created.configOptions;
|
|
1109
1114
|
advertisedModelId = created.models?.currentModelId;
|
|
1110
1115
|
this.captureRuntimeMetadata(advertisedConfigOptions, advertisedModelId);
|
|
@@ -1130,14 +1135,18 @@ export class AcpSession {
|
|
|
1130
1135
|
if (applied?.currentValue !== selection.value)
|
|
1131
1136
|
throw new Error(`ACP agent did not apply required session config option '${selection.configId}' value '${selection.value}'`);
|
|
1132
1137
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1138
|
+
if (this.options.requireMode) {
|
|
1139
|
+
if (!this.options.modeId?.trim())
|
|
1140
|
+
throw new Error('ACP required session mode requires a non-empty modeId');
|
|
1141
|
+
if (!advertisedModes?.availableModes.some(mode => mode.id === this.options.modeId))
|
|
1142
|
+
throw new Error(`ACP agent did not advertise required session mode '${this.options.modeId}'`);
|
|
1143
|
+
}
|
|
1144
|
+
// Preserve existing persistence timing for optional permission modes.
|
|
1145
|
+
if (!this.options.requireMode)
|
|
1146
|
+
writeFileSync(this.sessionFile, this.sessionId + '\n', { mode: 0o600 });
|
|
1137
1147
|
// Deliver the configured permission mode whichever way the session came up
|
|
1138
1148
|
// (new, resume or load) — the launch flag never reaches an ACP agent. A
|
|
1139
|
-
// refusal is
|
|
1140
|
-
// agent's own default.
|
|
1149
|
+
// refusal is fatal only when the adapter explicitly requires this mode.
|
|
1141
1150
|
if (this.options.modeId) {
|
|
1142
1151
|
try {
|
|
1143
1152
|
await this.connection.agent.request(acp.methods.agent.session.setMode, {
|
|
@@ -1146,10 +1155,16 @@ export class AcpSession {
|
|
|
1146
1155
|
});
|
|
1147
1156
|
}
|
|
1148
1157
|
catch (e) {
|
|
1158
|
+
if (this.options.requireMode)
|
|
1159
|
+
throw new Error(`ACP agent refused required session mode '${this.options.modeId}': `
|
|
1160
|
+
+ (e instanceof Error ? e.message : String(e)));
|
|
1149
1161
|
this.options.log(`[${this.options.name}] acp: session/set_mode "${this.options.modeId}" failed ` +
|
|
1150
1162
|
`(${e instanceof Error ? e.message : String(e)}) — session runs at the agent default permission mode`);
|
|
1151
1163
|
}
|
|
1152
1164
|
}
|
|
1165
|
+
// A required mode must succeed before its session becomes resumable.
|
|
1166
|
+
if (this.options.requireMode)
|
|
1167
|
+
writeFileSync(this.sessionFile, this.sessionId + '\n', { mode: 0o600 });
|
|
1153
1168
|
this.readiness = 'idle';
|
|
1154
1169
|
this.events.emit('state', { status: 'idle', text: `ACP session ${this.sessionId}` });
|
|
1155
1170
|
this.conversation.appendSafe({
|
|
@@ -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 ?? {};
|
package/dist/spawn.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type OpsDeps } from './ops.js';
|
|
|
5
5
|
import { type CreationDeps, type CreationProvenance } from './creation.js';
|
|
6
6
|
import './harness/claude-code.js';
|
|
7
7
|
import './harness/codex.js';
|
|
8
|
+
import './harness/hermes.js';
|
|
8
9
|
import { type SupervisorLauncher } from './temp-lifecycle.js';
|
|
9
10
|
/**
|
|
10
11
|
* The provenance record written by the most recent spawn in this process, so
|
package/dist/spawn.js
CHANGED
|
@@ -12,6 +12,7 @@ import { VERSION } from './version.js';
|
|
|
12
12
|
import { recordGeneratedAgentSource } from './generated-agent-source.js';
|
|
13
13
|
import './harness/claude-code.js';
|
|
14
14
|
import './harness/codex.js';
|
|
15
|
+
import './harness/hermes.js';
|
|
15
16
|
import { archiveTempState, makeTempSupervisorLauncher, prepareTempSupervisor, reclaimStaleTempState, } from './temp-lifecycle.js';
|
|
16
17
|
/**
|
|
17
18
|
* The provenance record written by the most recent spawn in this process, so
|
|
@@ -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.1
|
|
3
|
+
"version": "1.2.0-nightly.1",
|
|
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.1",
|
|
45
|
+
"@ours.network/sdk": "3.8.1-nightly.3",
|
|
45
46
|
"commander": "^12.1.0",
|
|
46
47
|
"fastify": "^5.4.0",
|
|
47
48
|
"react": "^19.1.1",
|