@phyzess/pappra 0.1.0-beta.13 → 0.1.0-beta.18
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 +134 -15
- package/assets/integrations/skills/pappra/SKILL.md +28 -7
- package/assets/integrations/skills/pappra/references/collaboration.md +633 -0
- package/assets/integrations/skills/pappra/references/environment.md +360 -0
- package/assets/integrations/skills/pappra/references/hosts.md +20 -0
- package/assets/integrations/skills/pappra/references/operations.md +36 -5
- package/dist/adapters/pi/recovery-extension.d.ts +65 -0
- package/dist/adapters/pi/recovery-extension.js +238 -0
- package/dist/adapters/pi/session-transition-extension.d.ts +30 -0
- package/dist/adapters/pi/session-transition-extension.js +265 -0
- package/dist/app/automatic-handoff-events.d.ts +26 -0
- package/dist/app/automatic-handoff-events.js +134 -0
- package/dist/app/bounded-file.d.ts +2 -0
- package/dist/app/bounded-file.js +2 -0
- package/dist/app/capability-evidence/claude.d.ts +37 -0
- package/dist/app/capability-evidence/claude.js +206 -0
- package/dist/app/capability-evidence/codex.d.ts +35 -0
- package/dist/app/capability-evidence/codex.js +193 -0
- package/dist/app/capability-evidence/index.d.ts +33 -0
- package/dist/app/capability-evidence/index.js +117 -0
- package/dist/app/capability-evidence/pi.d.ts +73 -0
- package/dist/app/capability-evidence/pi.js +669 -0
- package/dist/app/capability-evidence/shared.d.ts +69 -0
- package/dist/app/capability-evidence/shared.js +195 -0
- package/dist/app/collab-auto-handoff.d.ts +55 -0
- package/dist/app/collab-auto-handoff.js +384 -0
- package/dist/app/collab-bindings.d.ts +57 -0
- package/dist/app/collab-bindings.js +1006 -0
- package/dist/app/collab-bootstrap-transport.d.ts +163 -0
- package/dist/app/collab-bootstrap-transport.js +769 -0
- package/dist/app/collab-bootstrap.d.ts +137 -0
- package/dist/app/collab-bootstrap.js +3455 -0
- package/dist/app/collab-handoff.d.ts +12 -0
- package/dist/app/collab-handoff.js +511 -0
- package/dist/app/collab-messages.d.ts +2 -0
- package/dist/app/collab-messages.js +2 -0
- package/dist/app/collab-notify.d.ts +156 -0
- package/dist/app/collab-notify.js +1208 -0
- package/dist/app/collab-preflight.d.ts +34 -0
- package/dist/app/collab-preflight.js +96 -0
- package/dist/app/collab-recovery-executor.d.ts +161 -0
- package/dist/app/collab-recovery-executor.js +1153 -0
- package/dist/app/collab-recovery-projection.d.ts +103 -0
- package/dist/app/collab-recovery-projection.js +659 -0
- package/dist/app/collab-recovery.d.ts +25 -0
- package/dist/app/collab-recovery.js +429 -0
- package/dist/app/collab-resources.d.ts +16 -0
- package/dist/app/collab-resources.js +278 -0
- package/dist/app/collab-role-events.d.ts +22 -0
- package/dist/app/collab-role-events.js +329 -0
- package/dist/app/collab-role-orchestration.d.ts +53 -0
- package/dist/app/collab-role-orchestration.js +601 -0
- package/dist/app/collab-role-plan.d.ts +81 -0
- package/dist/app/collab-role-plan.js +504 -0
- package/dist/app/collab-role-repair.d.ts +109 -0
- package/dist/app/collab-role-repair.js +821 -0
- package/dist/app/collab-rollover.d.ts +263 -0
- package/dist/app/collab-rollover.js +1112 -0
- package/dist/app/collab-route.d.ts +64 -0
- package/dist/app/collab-route.js +240 -0
- package/dist/app/collab-session-transition.d.ts +221 -0
- package/dist/app/collab-session-transition.js +3334 -0
- package/dist/app/collab-state.d.ts +7 -0
- package/dist/app/collab-state.js +162 -0
- package/dist/app/collab-task-store.d.ts +22 -0
- package/dist/app/collab-task-store.js +204 -0
- package/dist/app/collab-work-execution.d.ts +127 -0
- package/dist/app/collab-work-execution.js +1027 -0
- package/dist/app/collab-work-plan.d.ts +87 -0
- package/dist/app/collab-work-plan.js +437 -0
- package/dist/app/collaboration.d.ts +8 -2
- package/dist/app/collaboration.js +101 -12
- package/dist/app/doctor.js +108 -5
- package/dist/app/host-mcp.js +34 -8
- package/dist/app/host-probe.js +4 -4
- package/dist/app/init.d.ts +58 -4
- package/dist/app/init.js +768 -155
- package/dist/app/managed-pi-bootstrap-admission.d.ts +14 -0
- package/dist/app/managed-pi-bootstrap-admission.js +104 -0
- package/dist/app/managed-pi-bootstrap-refresh.d.ts +10 -0
- package/dist/app/managed-pi-bootstrap-refresh.js +78 -0
- package/dist/app/managed-pi-delivery.d.ts +8 -0
- package/dist/app/managed-pi-delivery.js +110 -0
- package/dist/app/managed-pi-handoff-receiver.d.ts +15 -0
- package/dist/app/managed-pi-handoff-receiver.js +263 -0
- package/dist/app/managed-pi-handoff-reference.d.ts +3 -0
- package/dist/app/managed-pi-handoff-reference.js +60 -0
- package/dist/app/managed-pi-herdr.d.ts +21 -0
- package/dist/app/managed-pi-herdr.js +185 -0
- package/dist/app/managed-pi-initial-admission.d.ts +28 -0
- package/dist/app/managed-pi-initial-admission.js +416 -0
- package/dist/app/managed-pi-initial-launch.d.ts +5 -0
- package/dist/app/managed-pi-initial-launch.js +61 -0
- package/dist/app/managed-pi-native-observation.d.ts +36 -0
- package/dist/app/managed-pi-native-observation.js +191 -0
- package/dist/app/managed-pi-readiness.d.ts +29 -0
- package/dist/app/managed-pi-readiness.js +288 -0
- package/dist/app/managed-pi-repair-stop.d.ts +21 -0
- package/dist/app/managed-pi-repair-stop.js +371 -0
- package/dist/app/managed-pi-resume-admission.d.ts +32 -0
- package/dist/app/managed-pi-resume-admission.js +618 -0
- package/dist/app/managed-pi-retirement.d.ts +28 -0
- package/dist/app/managed-pi-retirement.js +112 -0
- package/dist/app/managed-pi-sdk-control.d.ts +7 -0
- package/dist/app/managed-pi-sdk-control.js +169 -0
- package/dist/app/managed-pi-sdk-runtime.d.ts +3 -0
- package/dist/app/managed-pi-sdk-runtime.js +483 -0
- package/dist/app/managed-pi-source-admission.d.ts +14 -0
- package/dist/app/managed-pi-source-admission.js +323 -0
- package/dist/app/managed-pi-transition-host.d.ts +36 -0
- package/dist/app/managed-pi-transition-host.js +292 -0
- package/dist/app/managed-pi.d.ts +54 -0
- package/dist/app/managed-pi.js +248 -0
- package/dist/app/shell-readiness.d.ts +66 -0
- package/dist/app/shell-readiness.js +368 -0
- package/dist/app/task-pin.d.ts +2 -23
- package/dist/app/task-pin.js +2 -113
- package/dist/cli/commands/collaboration-binding.d.ts +13 -0
- package/dist/cli/commands/collaboration-binding.js +118 -0
- package/dist/cli/commands/collaboration-bootstrap.d.ts +10 -0
- package/dist/cli/commands/collaboration-bootstrap.js +84 -0
- package/dist/cli/commands/collaboration-capabilities.d.ts +13 -0
- package/dist/cli/commands/collaboration-capabilities.js +127 -0
- package/dist/cli/commands/collaboration-handoff.d.ts +13 -0
- package/dist/cli/commands/collaboration-handoff.js +102 -0
- package/dist/cli/commands/collaboration-message.d.ts +17 -0
- package/dist/cli/commands/collaboration-message.js +177 -0
- package/dist/cli/commands/collaboration-plan.d.ts +8 -0
- package/dist/cli/commands/collaboration-plan.js +108 -0
- package/dist/cli/commands/collaboration-preflight.d.ts +10 -0
- package/dist/cli/commands/collaboration-preflight.js +167 -0
- package/dist/cli/commands/collaboration-recovery-run.d.ts +46 -0
- package/dist/cli/commands/collaboration-recovery-run.js +224 -0
- package/dist/cli/commands/collaboration-recovery.d.ts +11 -0
- package/dist/cli/commands/collaboration-recovery.js +104 -0
- package/dist/cli/commands/collaboration-resources.d.ts +11 -0
- package/dist/cli/commands/collaboration-resources.js +96 -0
- package/dist/cli/commands/collaboration-role-plan.d.ts +9 -0
- package/dist/cli/commands/collaboration-role-plan.js +69 -0
- package/dist/cli/commands/collaboration-role-run.d.ts +11 -0
- package/dist/cli/commands/collaboration-role-run.js +128 -0
- package/dist/cli/commands/collaboration-rollover.d.ts +10 -0
- package/dist/cli/commands/collaboration-rollover.js +103 -0
- package/dist/cli/commands/collaboration-state.d.ts +10 -0
- package/dist/cli/commands/collaboration-state.js +96 -0
- package/dist/cli/commands/collaboration-transition.d.ts +10 -0
- package/dist/cli/commands/collaboration-transition.js +75 -0
- package/dist/cli/commands/collaboration-work-run.d.ts +34 -0
- package/dist/cli/commands/collaboration-work-run.js +178 -0
- package/dist/cli/commands/collaboration.d.ts +4 -0
- package/dist/cli/commands/collaboration.js +39 -4
- package/dist/cli/commands/continuity.js +2 -6
- package/dist/cli/commands/docs.d.ts +2 -0
- package/dist/cli/commands/docs.js +89 -15
- package/dist/cli/commands/host.d.ts +7 -1
- package/dist/cli/commands/host.js +122 -7
- package/dist/cli/commands/init.d.ts +20 -3
- package/dist/cli/commands/init.js +897 -232
- package/dist/cli/commands/mcp.d.ts +1 -0
- package/dist/cli/commands/mcp.js +11 -5
- package/dist/cli/commands/repair.d.ts +12 -0
- package/dist/cli/commands/repair.js +41 -0
- package/dist/cli/commands/runtime.d.ts +1 -1
- package/dist/cli/commands/runtime.js +139 -38
- package/dist/cli/commands/session.d.ts +7 -0
- package/dist/cli/commands/session.js +82 -0
- package/dist/cli/commands/task.js +19 -5
- package/dist/cli/run.js +866 -24
- package/dist/code/identity.d.ts +7 -0
- package/dist/code/identity.js +7 -3
- package/dist/code/registration.js +7 -0
- package/dist/code/service.d.ts +9 -0
- package/dist/code/service.js +16 -3
- package/dist/code/text.js +19 -7
- package/dist/collab/admission.d.ts +91 -66
- package/dist/collab/admission.js +21 -6
- package/dist/collab/canonical.d.ts +1 -1
- package/dist/collab/canonical.js +44 -3
- package/dist/collab/envelope.d.ts +2 -2
- package/dist/collab/envelope.js +12 -12
- package/dist/collab/first-work.d.ts +3 -0
- package/dist/collab/first-work.js +15 -0
- package/dist/collab/handoff.d.ts +16 -0
- package/dist/collab/handoff.js +39 -5
- package/dist/collab/index.d.ts +97 -1
- package/dist/collab/index.js +9 -1
- package/dist/collab/messages.js +126 -3
- package/dist/collab/persisted-messages.d.ts +123 -0
- package/dist/collab/persisted-messages.js +496 -0
- package/dist/collab/preflight.d.ts +1 -1
- package/dist/collab/resources.d.ts +13 -3
- package/dist/collab/resources.js +37 -6
- package/dist/collab/rollover.d.ts +311 -0
- package/dist/collab/rollover.js +1024 -0
- package/dist/collab/store.d.ts +1 -0
- package/dist/collab/store.js +1 -4
- package/dist/collab/types.d.ts +2 -354
- package/dist/collab/validation.d.ts +2 -10
- package/dist/collab/validation.js +2 -303
- package/dist/collab/work-plan.d.ts +105 -0
- package/dist/collab/work-plan.js +843 -0
- package/dist/continuity/context.d.ts +11 -2
- package/dist/continuity/context.js +100 -13
- package/dist/continuity/handoff-bundle.d.ts +296 -0
- package/dist/continuity/handoff-bundle.js +477 -0
- package/dist/continuity/index.d.ts +2 -0
- package/dist/continuity/index.js +1 -0
- package/dist/continuity/paths.js +10 -2
- package/dist/continuity/service.js +91 -15
- package/dist/continuity/task-pin.d.ts +24 -0
- package/dist/continuity/task-pin.js +590 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +6 -0
- package/dist/experimental/index.d.ts +13 -0
- package/dist/experimental/index.js +25 -0
- package/dist/experimental/pi.d.ts +4 -0
- package/dist/experimental/pi.js +12 -0
- package/dist/hosts/claude/native.js +1 -1
- package/dist/hosts/codex/native.js +1 -1
- package/dist/hosts/codex/protocol.js +1 -1
- package/dist/hosts/integration/bootstrap-config.d.ts +58 -0
- package/dist/hosts/integration/bootstrap-config.js +254 -0
- package/dist/hosts/integration/bundle-export.d.ts +44 -0
- package/dist/hosts/integration/bundle-export.js +366 -3
- package/dist/hosts/integration/bundle.js +3 -0
- package/dist/hosts/integration/hook.js +36 -9
- package/dist/hosts/integration/pappra-bootstrap-extension.mjs +80 -0
- package/dist/hosts/integration/pappra-extension-runtime.mjs +59 -24631
- package/dist/hosts/integration/pappra-hook.mjs +61 -24749
- package/dist/hosts/integration/pappra-recovery-extension.mjs +259 -0
- package/dist/hosts/integration/pappra-session-transition-extension.mjs +276 -0
- package/dist/hosts/integration/pi-bootstrap-extension.d.ts +36 -0
- package/dist/hosts/integration/pi-bootstrap-extension.js +101 -0
- package/dist/hosts/integration/pi-bootstrap-guard.d.ts +125 -0
- package/dist/hosts/integration/pi-bootstrap-guard.js +438 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.d.ts +63 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.js +223 -0
- package/dist/hosts/integration/pi-native-events.d.ts +112 -0
- package/dist/hosts/integration/pi-native-events.js +348 -0
- package/dist/hosts/integration/pi-rollover-extension.d.ts +166 -0
- package/dist/hosts/integration/pi-rollover-extension.js +431 -0
- package/dist/hosts/integration/recovery-config.d.ts +48 -0
- package/dist/hosts/integration/recovery-config.js +117 -0
- package/dist/hosts/integration/service.d.ts +6 -1
- package/dist/hosts/integration/service.js +485 -35
- package/dist/hosts/integration/support.d.ts +7 -0
- package/dist/hosts/integration/support.js +9 -1
- package/dist/hosts/integration/templates.d.ts +3 -0
- package/dist/hosts/integration/templates.js +26 -3
- package/dist/hosts/pi/managed-host-identity.d.ts +2 -0
- package/dist/hosts/pi/managed-host-identity.js +40 -0
- package/dist/hosts/pi/managed-provider-diagnostics.d.ts +6 -0
- package/dist/hosts/pi/managed-provider-diagnostics.js +184 -0
- package/dist/hosts/pi/managed-sdk-session.d.ts +335 -0
- package/dist/hosts/pi/managed-sdk-session.js +1639 -0
- package/dist/hosts/pi/managed-session-extension.d.ts +20 -0
- package/dist/hosts/pi/managed-session-extension.js +84 -0
- package/dist/hosts/pi/managed-session.d.ts +177 -0
- package/dist/hosts/pi/managed-session.js +1557 -0
- package/dist/hosts/pi/protocol.js +1 -1
- package/dist/hosts/pi/query-result.js +1 -1
- package/dist/hosts/pi/resume-capability.d.ts +41 -0
- package/dist/hosts/pi/resume-capability.js +33 -0
- package/dist/hosts/pi/rpc.d.ts +182 -134
- package/dist/hosts/pi/rpc.js +230 -37
- package/dist/hosts/pilot.d.ts +97 -74
- package/dist/hosts/pilot.js +5 -1
- package/dist/hosts/version.d.ts +3 -0
- package/dist/hosts/version.js +18 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/bounded-file.d.ts +19 -0
- package/dist/infra/bounded-file.js +66 -0
- package/dist/infra/fs.js +10 -2
- package/dist/infra/git.d.ts +29 -0
- package/dist/infra/git.js +45 -9
- package/dist/infra/json-property.d.ts +2 -0
- package/dist/infra/json-property.js +69 -0
- package/dist/infra/json.d.ts +4 -0
- package/dist/infra/json.js +24 -0
- package/dist/infra/owned-process-group.js +1 -1
- package/dist/infra/private-lock.d.ts +16 -0
- package/dist/infra/private-lock.js +148 -0
- package/dist/infra/private-state.d.ts +7 -0
- package/dist/infra/private-state.js +146 -0
- package/dist/infra/process-identity.d.ts +12 -0
- package/dist/infra/process-identity.js +142 -0
- package/dist/infra/validation.d.ts +5 -0
- package/dist/infra/validation.js +15 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +6 -0
- package/dist/install/apply.d.ts +9 -1
- package/dist/install/apply.js +176 -12
- package/dist/install/dependencies.d.ts +38 -0
- package/dist/install/dependencies.js +282 -0
- package/dist/install/ownership.js +30 -1
- package/dist/install/references.d.ts +31 -0
- package/dist/install/references.js +208 -0
- package/dist/install/repair.d.ts +37 -0
- package/dist/install/repair.js +798 -0
- package/dist/install/reset-local.d.ts +16 -0
- package/dist/install/reset-local.js +304 -0
- package/dist/install/rollback.d.ts +3 -0
- package/dist/install/rollback.js +9 -3
- package/dist/install/runtime.d.ts +10 -6
- package/dist/install/runtime.js +299 -59
- package/dist/install/source-fingerprint.d.ts +2 -0
- package/dist/install/source-fingerprint.js +103 -0
- package/dist/knowledge/catalog.d.ts +1 -0
- package/dist/knowledge/catalog.js +143 -10
- package/dist/knowledge/journal.js +17 -7
- package/dist/knowledge/paths.js +2 -1
- package/dist/knowledge/refs.js +4 -0
- package/dist/knowledge/register-batch.d.ts +4 -0
- package/dist/knowledge/register-batch.js +225 -0
- package/dist/knowledge/relocate.d.ts +5 -0
- package/dist/knowledge/relocate.js +92 -0
- package/dist/knowledge/scan.js +103 -47
- package/dist/knowledge/service.d.ts +3 -0
- package/dist/knowledge/service.js +33 -1
- package/dist/knowledge/write.d.ts +4 -1
- package/dist/knowledge/write.js +1 -1
- package/dist/mcp/server.d.ts +4 -2
- package/dist/mcp/server.js +87 -136
- package/dist/schemas/collaboration-state-validation.d.ts +6 -0
- package/dist/schemas/collaboration-state-validation.js +342 -0
- package/dist/schemas/collaboration-state.d.ts +436 -0
- package/dist/schemas/collaboration-state.js +1 -0
- package/dist/schemas/collaboration.d.ts +335 -0
- package/dist/schemas/collaboration.js +272 -0
- package/dist/schemas/continuity.d.ts +8 -0
- package/dist/schemas/continuity.js +13 -3
- package/dist/schemas/doctor.d.ts +1 -0
- package/dist/schemas/doctor.js +1 -0
- package/dist/schemas/document.d.ts +61 -0
- package/dist/schemas/document.js +9 -0
- package/dist/schemas/errors.d.ts +1 -0
- package/dist/schemas/errors.js +1 -0
- package/dist/schemas/host.d.ts +24 -0
- package/dist/schemas/host.js +19 -0
- package/dist/schemas/install.d.ts +8 -2
- package/dist/schemas/install.js +11 -1
- package/dist/schemas/managed-pi-execution.d.ts +16 -0
- package/dist/schemas/managed-pi-execution.js +2 -0
- package/dist/schemas/outcome.d.ts +9 -5
- package/dist/workspace/config-file.js +1 -3
- package/dist/workspace/documents.js +12 -2
- package/dist/workspace/repositories.d.ts +7 -0
- package/dist/workspace/repositories.js +14 -5
- package/package.json +35 -35
- package/skills/pappra/SKILL.md +28 -7
- package/skills/pappra/references/collaboration.md +633 -0
- package/skills/pappra/references/environment.md +360 -0
- package/skills/pappra/references/hosts.md +20 -0
- package/skills/pappra/references/operations.md +36 -5
- package/dist/app/collab-from.d.ts +0 -35
- package/dist/app/collab-from.js +0 -84
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
import { object as isRecord } from '../infra/validation.js';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { managedPiSocketPeerPid } from './managed-pi-repair-stop.js';
|
|
4
|
+
import { readBoundedFile } from './bounded-file.js';
|
|
5
|
+
import { getPackageRoot } from '../package-info.js';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { createHerdrTransport, runHerdrCommand } from './collab-notify.js';
|
|
8
|
+
const READ_TIMEOUT_MS = 10_000;
|
|
9
|
+
const SPLIT_TIMEOUT_MS = 20_000;
|
|
10
|
+
const START_TIMEOUT_MS = 180_000;
|
|
11
|
+
const MAX_START_TIMEOUT_MS = 600_000;
|
|
12
|
+
const CLOSE_TIMEOUT_MS = 10_000;
|
|
13
|
+
const MAX_CAPTURE_CHARS = 32 * 1024;
|
|
14
|
+
const PANE_ID = /^[A-Za-z0-9]+:p[A-Za-z0-9]+$/;
|
|
15
|
+
const HERDR_AGENT_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
16
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
17
|
+
/** Fixed current-package SDK start command; only path arguments are data. */
|
|
18
|
+
export function managedSdkStartArgs(input) {
|
|
19
|
+
if (!path.isAbsolute(input.configPath) ||
|
|
20
|
+
path.resolve(input.configPath) !== input.configPath)
|
|
21
|
+
throw new Error('managed_sdk_command_argument_invalid');
|
|
22
|
+
const quote = (value) => {
|
|
23
|
+
if ([...value].some((character) => {
|
|
24
|
+
const code = character.charCodeAt(0);
|
|
25
|
+
return code === 0 || code === 10 || code === 13;
|
|
26
|
+
}))
|
|
27
|
+
throw new Error('managed_sdk_command_argument_invalid');
|
|
28
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
29
|
+
};
|
|
30
|
+
const command = `^${quote(process.execPath)} ${quote(path.join(getPackageRoot(), 'dist', 'cli', 'main.js'))} session start --config ${quote(input.configPath)}`;
|
|
31
|
+
return ['pane', 'run', input.paneId, command];
|
|
32
|
+
}
|
|
33
|
+
export function acceptedManagedSdkStartReceipt(receipt, input) {
|
|
34
|
+
const expected = managedSdkStartArgs(input);
|
|
35
|
+
return (receipt.operation === 'managed-sdk.start' &&
|
|
36
|
+
receipt.spawned &&
|
|
37
|
+
receipt.code === 0 &&
|
|
38
|
+
receipt.signal === null &&
|
|
39
|
+
!receipt.timed_out &&
|
|
40
|
+
receipt.spawn_error === null &&
|
|
41
|
+
receipt.argv.length === expected.length + 1 &&
|
|
42
|
+
receipt.argv[0] === 'herdr' &&
|
|
43
|
+
expected.every((argument, index) => receipt.argv[index + 1] === argument));
|
|
44
|
+
}
|
|
45
|
+
function nullableText(value) {
|
|
46
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
47
|
+
}
|
|
48
|
+
function parseEnvelope(result) {
|
|
49
|
+
const stdout = result.stdout.trim();
|
|
50
|
+
const stderr = result.stderr.trim();
|
|
51
|
+
const source = stdout.length > 0 ? stdout : stderr;
|
|
52
|
+
if (source.length === 0) {
|
|
53
|
+
return {
|
|
54
|
+
correlationId: null,
|
|
55
|
+
resultType: null,
|
|
56
|
+
result: null,
|
|
57
|
+
errorCode: null,
|
|
58
|
+
errorMessage: null,
|
|
59
|
+
parseError: 'empty_response',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(source);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return {
|
|
68
|
+
correlationId: null,
|
|
69
|
+
resultType: null,
|
|
70
|
+
result: null,
|
|
71
|
+
errorCode: null,
|
|
72
|
+
errorMessage: null,
|
|
73
|
+
parseError: 'invalid_json',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (!isRecord(parsed)) {
|
|
77
|
+
return {
|
|
78
|
+
correlationId: null,
|
|
79
|
+
resultType: null,
|
|
80
|
+
result: null,
|
|
81
|
+
errorCode: null,
|
|
82
|
+
errorMessage: null,
|
|
83
|
+
parseError: 'envelope_not_object',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const correlationId = nullableText(parsed['id']);
|
|
87
|
+
const error = parsed['error'];
|
|
88
|
+
const resultValue = parsed['result'];
|
|
89
|
+
if (isRecord(error)) {
|
|
90
|
+
return {
|
|
91
|
+
correlationId,
|
|
92
|
+
resultType: null,
|
|
93
|
+
result: null,
|
|
94
|
+
errorCode: nullableText(error['code']),
|
|
95
|
+
errorMessage: nullableText(error['message']),
|
|
96
|
+
parseError: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (!isRecord(resultValue)) {
|
|
100
|
+
return {
|
|
101
|
+
correlationId,
|
|
102
|
+
resultType: null,
|
|
103
|
+
result: null,
|
|
104
|
+
errorCode: null,
|
|
105
|
+
errorMessage: null,
|
|
106
|
+
parseError: 'result_missing',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
correlationId,
|
|
111
|
+
resultType: nullableText(resultValue['type']),
|
|
112
|
+
result: resultValue,
|
|
113
|
+
errorCode: null,
|
|
114
|
+
errorMessage: null,
|
|
115
|
+
parseError: null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function capture(operation, argv, result) {
|
|
119
|
+
return {
|
|
120
|
+
operation,
|
|
121
|
+
argv: [...argv],
|
|
122
|
+
code: result.code,
|
|
123
|
+
signal: result.signal,
|
|
124
|
+
spawned: result.spawned,
|
|
125
|
+
timed_out: result.timedOut,
|
|
126
|
+
spawn_error: result.spawnError,
|
|
127
|
+
stdout: result.stdout.slice(0, MAX_CAPTURE_CHARS),
|
|
128
|
+
stderr: result.stderr.slice(0, MAX_CAPTURE_CHARS),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function fail(status, code, detail, correlationId, receipt) {
|
|
132
|
+
return { ok: false, status, code, detail, correlationId, receipt };
|
|
133
|
+
}
|
|
134
|
+
function parsePane(value) {
|
|
135
|
+
if (!isRecord(value))
|
|
136
|
+
return null;
|
|
137
|
+
const paneId = nullableText(value['pane_id']);
|
|
138
|
+
const terminalId = nullableText(value['terminal_id']);
|
|
139
|
+
const workspaceId = nullableText(value['workspace_id']);
|
|
140
|
+
const tabId = nullableText(value['tab_id']);
|
|
141
|
+
const agentStatus = nullableText(value['agent_status']);
|
|
142
|
+
const revision = value['revision'];
|
|
143
|
+
if (paneId === null ||
|
|
144
|
+
!PANE_ID.test(paneId) ||
|
|
145
|
+
terminalId === null ||
|
|
146
|
+
workspaceId === null ||
|
|
147
|
+
tabId === null ||
|
|
148
|
+
agentStatus === null ||
|
|
149
|
+
typeof revision !== 'number' ||
|
|
150
|
+
!Number.isSafeInteger(revision) ||
|
|
151
|
+
revision < 0)
|
|
152
|
+
return null;
|
|
153
|
+
return {
|
|
154
|
+
paneId,
|
|
155
|
+
terminalId,
|
|
156
|
+
workspaceId,
|
|
157
|
+
tabId,
|
|
158
|
+
cwd: nullableText(value['cwd']),
|
|
159
|
+
foregroundCwd: nullableText(value['foreground_cwd']),
|
|
160
|
+
agent: nullableText(value['agent']),
|
|
161
|
+
agentStatus,
|
|
162
|
+
focused: value['focused'] === true,
|
|
163
|
+
revision,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function invoke(input) {
|
|
167
|
+
const argv = ['herdr', ...input.args];
|
|
168
|
+
let result;
|
|
169
|
+
try {
|
|
170
|
+
result = await input.run(input.bin, input.args, input.timeoutMs);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const receipt = {
|
|
174
|
+
operation: input.operation,
|
|
175
|
+
argv,
|
|
176
|
+
code: null,
|
|
177
|
+
signal: null,
|
|
178
|
+
spawned: false,
|
|
179
|
+
timed_out: false,
|
|
180
|
+
spawn_error: error instanceof Error ? error.message : String(error),
|
|
181
|
+
stdout: '',
|
|
182
|
+
stderr: '',
|
|
183
|
+
};
|
|
184
|
+
try {
|
|
185
|
+
await input.onReceipt?.(receipt);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// The launch caller will report the durable receipt failure as unknown.
|
|
189
|
+
}
|
|
190
|
+
return fail('unavailable', 'command_runner_failed', receipt.spawn_error ?? 'Command runner failed', null, receipt);
|
|
191
|
+
}
|
|
192
|
+
const receipt = capture(input.operation, argv, result);
|
|
193
|
+
try {
|
|
194
|
+
// For effectful calls, persist the bounded raw response before parsing it.
|
|
195
|
+
await input.onReceipt?.(receipt);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
return fail('unknown', 'receipt_persist_failed', error instanceof Error ? error.message : 'Receipt persistence failed', null, receipt);
|
|
199
|
+
}
|
|
200
|
+
const parsed = parseEnvelope(result);
|
|
201
|
+
if (!result.spawned) {
|
|
202
|
+
return fail('unavailable', 'herdr_not_callable', result.spawnError ?? 'Herdr command did not spawn', parsed.correlationId, receipt);
|
|
203
|
+
}
|
|
204
|
+
if (result.timedOut || result.signal !== null) {
|
|
205
|
+
return fail('unknown', 'herdr_command_timeout', 'Herdr command outcome is uncertain after timeout/signal', parsed.correlationId, receipt);
|
|
206
|
+
}
|
|
207
|
+
if (result.code !== 0) {
|
|
208
|
+
const startupMayHaveBegun = input.operation === 'agent.start' &&
|
|
209
|
+
['agent_not_ready', 'agent_launch_pending', 'agent_name_lost'].includes(parsed.errorCode ?? '');
|
|
210
|
+
return fail(startupMayHaveBegun || parsed.errorCode === null ? 'unknown' : 'failed', parsed.errorCode ?? 'herdr_command_failed', parsed.errorMessage ??
|
|
211
|
+
parsed.parseError ??
|
|
212
|
+
`Herdr exited ${String(result.code)}`, parsed.correlationId, receipt);
|
|
213
|
+
}
|
|
214
|
+
if (parsed.parseError !== null ||
|
|
215
|
+
parsed.result === null ||
|
|
216
|
+
parsed.resultType === null) {
|
|
217
|
+
return fail('failed', 'herdr_response_invalid', parsed.parseError ?? 'Herdr response result is invalid', parsed.correlationId, receipt);
|
|
218
|
+
}
|
|
219
|
+
if (parsed.resultType !== input.expectedType) {
|
|
220
|
+
return fail('failed', 'herdr_result_type_mismatch', `Expected ${input.expectedType}; received ${parsed.resultType}`, parsed.correlationId, receipt);
|
|
221
|
+
}
|
|
222
|
+
const selected = input.select(parsed.result);
|
|
223
|
+
if (selected === null) {
|
|
224
|
+
return fail('failed', 'herdr_payload_invalid', `Herdr ${input.operation} returned an invalid ${input.expectedType} payload`, parsed.correlationId, receipt);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
ok: true,
|
|
228
|
+
value: selected,
|
|
229
|
+
correlationId: parsed.correlationId,
|
|
230
|
+
receipt,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function parseManagedSdkControlStatus(result) {
|
|
234
|
+
if (!result.spawned ||
|
|
235
|
+
result.timedOut ||
|
|
236
|
+
result.signal !== null ||
|
|
237
|
+
result.code !== 0)
|
|
238
|
+
return null;
|
|
239
|
+
let value;
|
|
240
|
+
try {
|
|
241
|
+
value = JSON.parse(result.stdout.trim());
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
if (!isRecord(value) || value['ok'] !== true)
|
|
247
|
+
return null;
|
|
248
|
+
const snapshot = value['snapshot'];
|
|
249
|
+
const automation = value['automation'];
|
|
250
|
+
if (!isRecord(snapshot) || !isRecord(automation))
|
|
251
|
+
return null;
|
|
252
|
+
const process = snapshot['process'];
|
|
253
|
+
const session = snapshot['session'];
|
|
254
|
+
const ingress = snapshot['ingress'];
|
|
255
|
+
const launchValue = automation['launch'];
|
|
256
|
+
if (!isRecord(process) ||
|
|
257
|
+
!isRecord(session) ||
|
|
258
|
+
!isRecord(ingress) ||
|
|
259
|
+
!isRecord(launchValue) ||
|
|
260
|
+
snapshot['schema'] !== 'harness-collab/pi-managed-sdk-session/1' ||
|
|
261
|
+
process['exit'] !== 'not_owned' ||
|
|
262
|
+
typeof process['pid'] !== 'number' ||
|
|
263
|
+
!Number.isSafeInteger(process['pid']) ||
|
|
264
|
+
process['pid'] <= 0 ||
|
|
265
|
+
launchValue['runtime'] !== 'sdk' ||
|
|
266
|
+
launchValue['ready'] !== true ||
|
|
267
|
+
typeof launchValue['sdk_module_path'] !== 'string' ||
|
|
268
|
+
typeof launchValue['bootstrap_extension_path'] !== 'string' ||
|
|
269
|
+
typeof launchValue['herdr_agent_name'] !== 'string' ||
|
|
270
|
+
typeof launchValue['journal_path'] !== 'string' ||
|
|
271
|
+
typeof launchValue['session_id'] !== 'string' ||
|
|
272
|
+
typeof launchValue['cwd'] !== 'string' ||
|
|
273
|
+
!isRecord(launchValue['model']) ||
|
|
274
|
+
launchValue['model']['provider'] !== 'openrouter' ||
|
|
275
|
+
typeof launchValue['model']['id'] !== 'string' ||
|
|
276
|
+
session['sessionId'] !== launchValue['session_id'] ||
|
|
277
|
+
ingress['journalPath'] !== launchValue['journal_path'])
|
|
278
|
+
return null;
|
|
279
|
+
return {
|
|
280
|
+
processPid: process['pid'],
|
|
281
|
+
controlSchema: 'harness-collab/pi-managed-sdk-session/1',
|
|
282
|
+
launch: {
|
|
283
|
+
runtime: 'sdk',
|
|
284
|
+
sdkModulePath: launchValue['sdk_module_path'],
|
|
285
|
+
bootstrapExtensionPath: launchValue['bootstrap_extension_path'],
|
|
286
|
+
herdrAgentName: launchValue['herdr_agent_name'],
|
|
287
|
+
journalPath: launchValue['journal_path'],
|
|
288
|
+
sessionId: launchValue['session_id'],
|
|
289
|
+
cwd: launchValue['cwd'],
|
|
290
|
+
model: {
|
|
291
|
+
provider: 'openrouter',
|
|
292
|
+
id: launchValue['model']['id'],
|
|
293
|
+
},
|
|
294
|
+
ready: true,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function sameRealPath(left, right) {
|
|
299
|
+
try {
|
|
300
|
+
return fs.realpathSync(left) === fs.realpathSync(right);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function privateManagedConfigMatches(input) {
|
|
307
|
+
const loaded = readBoundedFile(input.configPath, 64 * 1024);
|
|
308
|
+
if (!loaded.ok ||
|
|
309
|
+
loaded.digest !== input.configDigest ||
|
|
310
|
+
!/^sha256:[0-9a-f]{64}$/.test(input.configDigest))
|
|
311
|
+
return false;
|
|
312
|
+
let value;
|
|
313
|
+
try {
|
|
314
|
+
value = JSON.parse(loaded.text);
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
if (!isRecord(value))
|
|
320
|
+
return false;
|
|
321
|
+
const model = value['model'];
|
|
322
|
+
if (value['schema'] !== 'pappra/managed-pi-session-request/1' ||
|
|
323
|
+
value['runtime'] !== 'sdk' ||
|
|
324
|
+
value['cwd'] !== input.cwd ||
|
|
325
|
+
value['sessionId'] !== input.sessionId ||
|
|
326
|
+
value['sdkModulePath'] !== input.sdkModulePath ||
|
|
327
|
+
value['journalPath'] !== input.journalPath ||
|
|
328
|
+
value['controlSocketPath'] !== input.controlSocketPath ||
|
|
329
|
+
value['bootstrapExtensionPath'] !== input.bootstrapExtensionPath ||
|
|
330
|
+
value['herdrAgentName'] !== input.name ||
|
|
331
|
+
value['herdrPaneId'] !== input.paneId ||
|
|
332
|
+
model !== input.openrouterModel ||
|
|
333
|
+
Object.hasOwn(value, 'executionScope') ||
|
|
334
|
+
(input.automaticHandoffFile === undefined
|
|
335
|
+
? Object.hasOwn(value, 'automaticHandoffFile')
|
|
336
|
+
: value['automaticHandoffFile'] !== input.automaticHandoffFile) ||
|
|
337
|
+
(input.handoffContextFile === undefined
|
|
338
|
+
? Object.hasOwn(value, 'handoffContextFile')
|
|
339
|
+
: value['handoffContextFile'] !== input.handoffContextFile))
|
|
340
|
+
return false;
|
|
341
|
+
try {
|
|
342
|
+
const directory = path.dirname(input.configPath);
|
|
343
|
+
const directoryStat = fs.lstatSync(directory);
|
|
344
|
+
const configStat = fs.lstatSync(input.configPath);
|
|
345
|
+
return (directoryStat.isDirectory() &&
|
|
346
|
+
!directoryStat.isSymbolicLink() &&
|
|
347
|
+
fs.realpathSync(directory) === directory &&
|
|
348
|
+
(directoryStat.mode & 0o077) === 0 &&
|
|
349
|
+
(typeof process.getuid !== 'function' ||
|
|
350
|
+
directoryStat.uid === process.getuid()) &&
|
|
351
|
+
configStat.isFile() &&
|
|
352
|
+
!configStat.isSymbolicLink() &&
|
|
353
|
+
(configStat.mode & 0o777) === 0o400 &&
|
|
354
|
+
(typeof process.getuid !== 'function' ||
|
|
355
|
+
configStat.uid === process.getuid()));
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
export function createHerdrBootstrapOps(options = {}) {
|
|
362
|
+
const env = options.env ?? process.env;
|
|
363
|
+
const run = options.run ?? runHerdrCommand;
|
|
364
|
+
const transport = options.transport ??
|
|
365
|
+
createHerdrTransport({
|
|
366
|
+
env,
|
|
367
|
+
run,
|
|
368
|
+
...(options.taskRoot === undefined ? {} : { taskRoot: options.taskRoot }),
|
|
369
|
+
});
|
|
370
|
+
const socketPeerPid = options.peerPid ?? managedPiSocketPeerPid;
|
|
371
|
+
const bin = typeof env['HERDR_BIN_PATH'] === 'string' &&
|
|
372
|
+
env['HERDR_BIN_PATH'].trim() !== ''
|
|
373
|
+
? env['HERDR_BIN_PATH']
|
|
374
|
+
: 'herdr';
|
|
375
|
+
const ops = {
|
|
376
|
+
id: 'herdr-bootstrap',
|
|
377
|
+
transport,
|
|
378
|
+
verifyCaller: (paneId) => transport.verifyCaller(paneId === undefined || paneId === null ? {} : { paneId }),
|
|
379
|
+
listAgents: () => transport.list(),
|
|
380
|
+
paneGet: (paneId, timeoutMs = READ_TIMEOUT_MS) => invoke({
|
|
381
|
+
run,
|
|
382
|
+
bin,
|
|
383
|
+
operation: 'pane.get',
|
|
384
|
+
args: ['pane', 'get', paneId],
|
|
385
|
+
timeoutMs,
|
|
386
|
+
expectedType: 'pane_info',
|
|
387
|
+
select: (result) => parsePane(result['pane']),
|
|
388
|
+
}),
|
|
389
|
+
paneProcessInfo: (paneId, timeoutMs = READ_TIMEOUT_MS) => invoke({
|
|
390
|
+
run,
|
|
391
|
+
bin,
|
|
392
|
+
operation: 'pane.process-info',
|
|
393
|
+
args: ['pane', 'process-info', '--pane', paneId],
|
|
394
|
+
timeoutMs,
|
|
395
|
+
expectedType: 'pane_process_info',
|
|
396
|
+
select: (result) => {
|
|
397
|
+
const value = result['process_info'];
|
|
398
|
+
if (!isRecord(value) || value['pane_id'] !== paneId)
|
|
399
|
+
return null;
|
|
400
|
+
const shellPid = value['shell_pid'];
|
|
401
|
+
const foregroundProcessGroupId = value['foreground_process_group_id'];
|
|
402
|
+
if (shellPid !== undefined &&
|
|
403
|
+
shellPid !== null &&
|
|
404
|
+
(typeof shellPid !== 'number' ||
|
|
405
|
+
!Number.isSafeInteger(shellPid) ||
|
|
406
|
+
shellPid <= 0))
|
|
407
|
+
return null;
|
|
408
|
+
if (foregroundProcessGroupId !== undefined &&
|
|
409
|
+
foregroundProcessGroupId !== null &&
|
|
410
|
+
(typeof foregroundProcessGroupId !== 'number' ||
|
|
411
|
+
!Number.isSafeInteger(foregroundProcessGroupId) ||
|
|
412
|
+
foregroundProcessGroupId <= 0))
|
|
413
|
+
return null;
|
|
414
|
+
const rawProcesses = value['foreground_processes'];
|
|
415
|
+
if (rawProcesses !== undefined && !Array.isArray(rawProcesses))
|
|
416
|
+
return null;
|
|
417
|
+
const foregroundProcesses = [];
|
|
418
|
+
for (const item of rawProcesses ?? []) {
|
|
419
|
+
if (!isRecord(item) ||
|
|
420
|
+
!Number.isSafeInteger(item['pid']) ||
|
|
421
|
+
item['pid'] <= 0 ||
|
|
422
|
+
typeof item['name'] !== 'string')
|
|
423
|
+
return null;
|
|
424
|
+
foregroundProcesses.push({
|
|
425
|
+
pid: item['pid'],
|
|
426
|
+
name: item['name'],
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
paneId,
|
|
431
|
+
shellPid: typeof shellPid === 'number' ? shellPid : null,
|
|
432
|
+
foregroundProcessGroupId: typeof foregroundProcessGroupId === 'number'
|
|
433
|
+
? foregroundProcessGroupId
|
|
434
|
+
: null,
|
|
435
|
+
tty: nullableText(value['tty']),
|
|
436
|
+
foregroundProcesses,
|
|
437
|
+
};
|
|
438
|
+
},
|
|
439
|
+
}),
|
|
440
|
+
splitPane: ({ paneId, cwd, direction, timeoutMs = SPLIT_TIMEOUT_MS, onReceipt, }) => {
|
|
441
|
+
if (!PANE_ID.test(paneId))
|
|
442
|
+
return Promise.resolve(fail('failed', 'pane_id_invalid', 'Caller pane id is invalid', null, emptyReceipt('pane.split')));
|
|
443
|
+
if (!path.isAbsolute(cwd))
|
|
444
|
+
return Promise.resolve(fail('failed', 'cwd_invalid', 'Split cwd must be absolute', null, emptyReceipt('pane.split')));
|
|
445
|
+
const args = [
|
|
446
|
+
'pane',
|
|
447
|
+
'split',
|
|
448
|
+
'--pane',
|
|
449
|
+
paneId,
|
|
450
|
+
'--cwd',
|
|
451
|
+
cwd,
|
|
452
|
+
'--no-focus',
|
|
453
|
+
];
|
|
454
|
+
if (direction !== undefined)
|
|
455
|
+
args.push('--direction', direction);
|
|
456
|
+
return invoke({
|
|
457
|
+
run,
|
|
458
|
+
bin,
|
|
459
|
+
operation: 'pane.split',
|
|
460
|
+
args,
|
|
461
|
+
timeoutMs,
|
|
462
|
+
expectedType: 'pane_info',
|
|
463
|
+
select: (result) => parsePane(result['pane']),
|
|
464
|
+
...(onReceipt === undefined ? {} : { onReceipt }),
|
|
465
|
+
});
|
|
466
|
+
},
|
|
467
|
+
startAgent: ({ name, paneId, sessionId, extensionEntry, openrouterModel, timeoutMs = START_TIMEOUT_MS, onReceipt, }) => {
|
|
468
|
+
if (!HERDR_AGENT_NAME.test(name))
|
|
469
|
+
return Promise.resolve(fail('failed', 'agent_name_invalid', 'Herdr agent name is invalid', null, emptyReceipt('agent.start')));
|
|
470
|
+
if (!PANE_ID.test(paneId))
|
|
471
|
+
return Promise.resolve(fail('failed', 'pane_id_invalid', 'Target pane id is invalid', null, emptyReceipt('agent.start')));
|
|
472
|
+
if (!UUID.test(sessionId))
|
|
473
|
+
return Promise.resolve(fail('failed', 'session_id_invalid', 'Successor session selector must be a UUID', null, emptyReceipt('agent.start')));
|
|
474
|
+
if (!path.isAbsolute(extensionEntry))
|
|
475
|
+
return Promise.resolve(fail('failed', 'extension_invalid', 'Pi extension path must be absolute', null, emptyReceipt('agent.start')));
|
|
476
|
+
if (openrouterModel !== undefined &&
|
|
477
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/.test(openrouterModel))
|
|
478
|
+
return Promise.resolve(fail('failed', 'model_invalid', 'OpenRouter model identifier is invalid', null, emptyReceipt('agent.start')));
|
|
479
|
+
const args = [
|
|
480
|
+
'agent',
|
|
481
|
+
'start',
|
|
482
|
+
name,
|
|
483
|
+
'--kind',
|
|
484
|
+
'pi',
|
|
485
|
+
'--pane',
|
|
486
|
+
paneId,
|
|
487
|
+
'--timeout',
|
|
488
|
+
String(timeoutMs),
|
|
489
|
+
'--',
|
|
490
|
+
'--session-id',
|
|
491
|
+
sessionId,
|
|
492
|
+
'--name',
|
|
493
|
+
name,
|
|
494
|
+
'--extension',
|
|
495
|
+
extensionEntry,
|
|
496
|
+
...(openrouterModel
|
|
497
|
+
? ['--provider', 'openrouter', '--model', openrouterModel]
|
|
498
|
+
: []),
|
|
499
|
+
];
|
|
500
|
+
return invoke({
|
|
501
|
+
run,
|
|
502
|
+
bin,
|
|
503
|
+
operation: 'agent.start',
|
|
504
|
+
args,
|
|
505
|
+
timeoutMs: timeoutMs + 5_000,
|
|
506
|
+
expectedType: 'agent_started',
|
|
507
|
+
select: (result) => {
|
|
508
|
+
const agent = result['agent'];
|
|
509
|
+
const argv = result['argv'];
|
|
510
|
+
if (!isRecord(agent) ||
|
|
511
|
+
!Array.isArray(argv) ||
|
|
512
|
+
argv.some((item) => typeof item !== 'string'))
|
|
513
|
+
return null;
|
|
514
|
+
const paneInfo = parsePane(agent);
|
|
515
|
+
if (paneInfo === null || paneInfo.paneId !== paneId)
|
|
516
|
+
return null;
|
|
517
|
+
const session = agent['agent_session'];
|
|
518
|
+
const sessionKind = isRecord(session)
|
|
519
|
+
? nullableText(session['kind'])
|
|
520
|
+
: null;
|
|
521
|
+
const sessionValue = isRecord(session)
|
|
522
|
+
? nullableText(session['value'])
|
|
523
|
+
: null;
|
|
524
|
+
return {
|
|
525
|
+
name: nullableText(agent['name']),
|
|
526
|
+
harness: nullableText(agent['agent']),
|
|
527
|
+
paneId: paneInfo.paneId,
|
|
528
|
+
workspaceId: paneInfo.workspaceId,
|
|
529
|
+
terminalId: paneInfo.terminalId,
|
|
530
|
+
cwd: paneInfo.cwd,
|
|
531
|
+
agentStatus: paneInfo.agentStatus,
|
|
532
|
+
interactiveReady: agent['interactive_ready'] === true,
|
|
533
|
+
launchPending: agent['launch_pending'] === true,
|
|
534
|
+
sessionKind,
|
|
535
|
+
sessionValue,
|
|
536
|
+
argv: argv,
|
|
537
|
+
};
|
|
538
|
+
},
|
|
539
|
+
...(onReceipt === undefined ? {} : { onReceipt }),
|
|
540
|
+
});
|
|
541
|
+
},
|
|
542
|
+
startManagedSdk: async (input) => {
|
|
543
|
+
const timeoutMs = input.timeoutMs ?? START_TIMEOUT_MS;
|
|
544
|
+
const empty = emptyReceipt('managed-sdk.start');
|
|
545
|
+
const failManaged = (status, code, detail, receipt = empty, correlationId = null) => fail(status, code, detail, correlationId, receipt);
|
|
546
|
+
if (!HERDR_AGENT_NAME.test(input.name) ||
|
|
547
|
+
!PANE_ID.test(input.paneId) ||
|
|
548
|
+
!UUID.test(input.sessionId) ||
|
|
549
|
+
!path.isAbsolute(input.cwd) ||
|
|
550
|
+
!path.isAbsolute(input.sdkModulePath) ||
|
|
551
|
+
!path.isAbsolute(input.journalPath) ||
|
|
552
|
+
!path.isAbsolute(input.controlSocketPath) ||
|
|
553
|
+
!path.isAbsolute(input.bootstrapExtensionPath) ||
|
|
554
|
+
!path.isAbsolute(input.configPath) ||
|
|
555
|
+
(input.handoffContextFile !== undefined &&
|
|
556
|
+
(!path.isAbsolute(input.handoffContextFile) ||
|
|
557
|
+
path.resolve(input.handoffContextFile) !==
|
|
558
|
+
input.handoffContextFile ||
|
|
559
|
+
[...input.handoffContextFile].some((character) => {
|
|
560
|
+
const code = character.charCodeAt(0);
|
|
561
|
+
return code === 0 || code === 10 || code === 13;
|
|
562
|
+
}))) ||
|
|
563
|
+
Buffer.byteLength(input.controlSocketPath) > 100 ||
|
|
564
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/.test(input.openrouterModel) ||
|
|
565
|
+
!Number.isSafeInteger(timeoutMs) ||
|
|
566
|
+
timeoutMs < 1 ||
|
|
567
|
+
timeoutMs > MAX_START_TIMEOUT_MS)
|
|
568
|
+
return failManaged('failed', 'managed_start_input_invalid', 'Managed SDK start input failed bounded path, identity or model validation.');
|
|
569
|
+
if (!privateManagedConfigMatches(input))
|
|
570
|
+
return failManaged('failed', 'managed_config_invalid', 'Managed SDK config is not the exact private immutable request reserved by bootstrap.');
|
|
571
|
+
let args;
|
|
572
|
+
try {
|
|
573
|
+
args = managedSdkStartArgs(input);
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
return failManaged('failed', 'managed_start_command_invalid', 'Managed SDK command could not be safely encoded as fixed Nushell arguments.');
|
|
577
|
+
}
|
|
578
|
+
const commandArgv = ['herdr', ...args];
|
|
579
|
+
let submitted;
|
|
580
|
+
try {
|
|
581
|
+
submitted = await run(bin, args, timeoutMs + 5_000);
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
const receipt = {
|
|
585
|
+
...empty,
|
|
586
|
+
argv: commandArgv,
|
|
587
|
+
spawn_error: 'command_runner_failed',
|
|
588
|
+
};
|
|
589
|
+
try {
|
|
590
|
+
await input.onReceipt?.(receipt);
|
|
591
|
+
}
|
|
592
|
+
catch {
|
|
593
|
+
return failManaged('unknown', 'managed_start_receipt_persist_failed', 'Managed SDK submission outcome is unknown because its receipt was not persisted.', receipt);
|
|
594
|
+
}
|
|
595
|
+
return failManaged('unavailable', 'managed_start_runner_failed', 'Herdr pane run was unavailable before a command could be queued.', receipt);
|
|
596
|
+
}
|
|
597
|
+
const receipt = capture('managed-sdk.start', commandArgv, submitted);
|
|
598
|
+
try {
|
|
599
|
+
await input.onReceipt?.(receipt);
|
|
600
|
+
}
|
|
601
|
+
catch {
|
|
602
|
+
return failManaged('unknown', 'managed_start_receipt_persist_failed', 'Managed SDK submission outcome is unknown because its receipt was not persisted.', receipt);
|
|
603
|
+
}
|
|
604
|
+
if (!submitted.spawned)
|
|
605
|
+
return failManaged('unavailable', 'herdr_not_callable', 'Herdr did not spawn the managed pane-run command.', receipt);
|
|
606
|
+
if (submitted.timedOut ||
|
|
607
|
+
submitted.signal !== null ||
|
|
608
|
+
submitted.code !== 0)
|
|
609
|
+
return failManaged('unknown', 'managed_start_submission_unknown', 'Herdr pane-run may have queued the managed CLI; startup must be reconciled without resubmission.', receipt);
|
|
610
|
+
const queued = parseEnvelope(submitted);
|
|
611
|
+
// Herdr 0.9.5 acknowledges pane-run with exit 0 and empty stdout. This
|
|
612
|
+
// proves queue submission only; authenticated startup evidence is still required.
|
|
613
|
+
const emptyQueueReceipt = submitted.stdout.trim().length === 0;
|
|
614
|
+
if (!emptyQueueReceipt &&
|
|
615
|
+
(queued.parseError !== null ||
|
|
616
|
+
queued.result === null ||
|
|
617
|
+
queued.resultType === null))
|
|
618
|
+
return failManaged('unknown', 'managed_start_submission_unconfirmed', 'Herdr pane-run response did not confirm a typed submission; no retry is permitted.', receipt, queued.correlationId);
|
|
619
|
+
const responsePane = queued.result?.['pane_id'];
|
|
620
|
+
if (responsePane !== undefined && responsePane !== input.paneId)
|
|
621
|
+
return failManaged('unknown', 'managed_start_pane_mismatch', 'Herdr pane-run response identified a different pane.', receipt, queued.correlationId);
|
|
622
|
+
const cliPath = path.join(getPackageRoot(), 'dist', 'cli', 'main.js');
|
|
623
|
+
const deadline = Date.now() + timeoutMs;
|
|
624
|
+
let lastDetail = 'Waiting for owned SDK control and Herdr identity evidence.';
|
|
625
|
+
while (Date.now() <= deadline) {
|
|
626
|
+
const listed = await transport.list();
|
|
627
|
+
if (!listed.ok) {
|
|
628
|
+
lastDetail = 'Herdr agent list is temporarily unavailable.';
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
const named = listed.agents.filter((agent) => agent.name === input.name);
|
|
632
|
+
if (named.length > 1)
|
|
633
|
+
return failManaged('unknown', 'managed_herdr_name_ambiguous', 'More than one Herdr observation claims the managed agent name.', receipt, queued.correlationId);
|
|
634
|
+
const agent = named[0];
|
|
635
|
+
if (agent !== undefined) {
|
|
636
|
+
if ((agent.paneId !== null && agent.paneId !== input.paneId) ||
|
|
637
|
+
(agent.cwd !== null && !sameRealPath(agent.cwd, input.cwd)) ||
|
|
638
|
+
(agent.harness !== null && agent.harness !== 'pi'))
|
|
639
|
+
return failManaged('unknown', 'managed_herdr_identity_mismatch', 'The named Herdr observation belongs to a different pane, cwd or harness.', receipt, queued.correlationId);
|
|
640
|
+
const status = agent.status.trim().toLowerCase();
|
|
641
|
+
if (agent.paneId === input.paneId &&
|
|
642
|
+
agent.harness === 'pi' &&
|
|
643
|
+
agent.cwd !== null &&
|
|
644
|
+
sameRealPath(agent.cwd, input.cwd) &&
|
|
645
|
+
['idle', 'done', 'working', 'running'].includes(status)) {
|
|
646
|
+
const pane = await ops.paneGet(input.paneId, READ_TIMEOUT_MS);
|
|
647
|
+
if (!pane.ok) {
|
|
648
|
+
lastDetail = 'Owned pane identity is not yet observable.';
|
|
649
|
+
}
|
|
650
|
+
else if (pane.value.paneId !== input.paneId ||
|
|
651
|
+
pane.value.workspaceId !== input.workspaceId ||
|
|
652
|
+
pane.value.terminalId !== input.terminalId ||
|
|
653
|
+
pane.value.cwd === null ||
|
|
654
|
+
!sameRealPath(pane.value.cwd, input.cwd)) {
|
|
655
|
+
return failManaged('unknown', 'managed_pane_identity_mismatch', 'Herdr pane identity or cwd changed during managed SDK startup.', receipt, queued.correlationId);
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
let statusResult = null;
|
|
659
|
+
try {
|
|
660
|
+
statusResult = await run(process.execPath, [
|
|
661
|
+
cliPath,
|
|
662
|
+
'session',
|
|
663
|
+
'status',
|
|
664
|
+
'--config',
|
|
665
|
+
input.configPath,
|
|
666
|
+
'--json',
|
|
667
|
+
], READ_TIMEOUT_MS);
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
lastDetail =
|
|
671
|
+
'Authenticated managed SDK control is not yet reachable.';
|
|
672
|
+
}
|
|
673
|
+
const control = statusResult === null
|
|
674
|
+
? null
|
|
675
|
+
: parseManagedSdkControlStatus(statusResult);
|
|
676
|
+
if (control !== null) {
|
|
677
|
+
const launch = control.launch;
|
|
678
|
+
if (launch.sdkModulePath !== input.sdkModulePath ||
|
|
679
|
+
launch.bootstrapExtensionPath !==
|
|
680
|
+
input.bootstrapExtensionPath ||
|
|
681
|
+
launch.herdrAgentName !== input.name ||
|
|
682
|
+
launch.journalPath !== input.journalPath ||
|
|
683
|
+
launch.sessionId !== input.sessionId ||
|
|
684
|
+
!sameRealPath(launch.cwd, input.cwd) ||
|
|
685
|
+
launch.model.provider !== 'openrouter' ||
|
|
686
|
+
launch.model.id !== input.openrouterModel ||
|
|
687
|
+
launch.ready !== true)
|
|
688
|
+
return failManaged('unknown', 'managed_sdk_control_mismatch', 'Authenticated SDK control metadata did not match the reserved managed launch.', receipt, queued.correlationId);
|
|
689
|
+
const process = await ops.paneProcessInfo(input.paneId, READ_TIMEOUT_MS);
|
|
690
|
+
if (!process.ok) {
|
|
691
|
+
lastDetail =
|
|
692
|
+
'Owned pane foreground process is not yet observable.';
|
|
693
|
+
}
|
|
694
|
+
else if (process.value.paneId !== input.paneId) {
|
|
695
|
+
return failManaged('unknown', 'managed_process_info_pane_mismatch', 'Herdr process-info returned a different pane.', receipt, queued.correlationId);
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
const socketPid = socketPeerPid(input.controlSocketPath);
|
|
699
|
+
const foreground = process.value.foregroundProcesses.find((item) => item.pid === control.processPid);
|
|
700
|
+
if (socketPid !== null && socketPid !== control.processPid)
|
|
701
|
+
return failManaged('unknown', 'managed_control_socket_owner_mismatch', 'The managed control socket is owned by a different OS process.', receipt, queued.correlationId);
|
|
702
|
+
if (socketPid === control.processPid &&
|
|
703
|
+
foreground !== undefined) {
|
|
704
|
+
return {
|
|
705
|
+
ok: true,
|
|
706
|
+
value: {
|
|
707
|
+
agentName: agent.name,
|
|
708
|
+
harness: agent.harness,
|
|
709
|
+
paneId: input.paneId,
|
|
710
|
+
cwd: agent.cwd,
|
|
711
|
+
status: agent.status,
|
|
712
|
+
sessionId: control.launch.sessionId,
|
|
713
|
+
identityProvenance: 'owned-pi-sdk-control',
|
|
714
|
+
controlSchema: control.controlSchema,
|
|
715
|
+
processPid: control.processPid,
|
|
716
|
+
socketPeerPid: socketPid,
|
|
717
|
+
foregroundProcessPid: foreground.pid,
|
|
718
|
+
pane: pane.value,
|
|
719
|
+
launch: control.launch,
|
|
720
|
+
},
|
|
721
|
+
correlationId: queued.correlationId,
|
|
722
|
+
receipt,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
lastDetail =
|
|
726
|
+
'SDK control, socket owner and pane foreground PID do not yet agree.';
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const remaining = deadline - Date.now();
|
|
734
|
+
if (remaining <= 0)
|
|
735
|
+
break;
|
|
736
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(100, remaining)));
|
|
737
|
+
}
|
|
738
|
+
return failManaged('unknown', 'managed_sdk_start_observation_timeout', `${lastDetail} No resubmission or cleanup is permitted for this request id.`, receipt, queued.correlationId);
|
|
739
|
+
},
|
|
740
|
+
closePane: ({ paneId, timeoutMs = CLOSE_TIMEOUT_MS, onReceipt }) => {
|
|
741
|
+
if (!PANE_ID.test(paneId))
|
|
742
|
+
return Promise.resolve(fail('failed', 'pane_id_invalid', 'Owned pane id is invalid', null, emptyReceipt('pane.close')));
|
|
743
|
+
return invoke({
|
|
744
|
+
run,
|
|
745
|
+
bin,
|
|
746
|
+
operation: 'pane.close',
|
|
747
|
+
args: ['pane', 'close', paneId],
|
|
748
|
+
timeoutMs,
|
|
749
|
+
expectedType: 'ok',
|
|
750
|
+
select: (result) => result['type'] === 'ok' ? { paneId, closed: true } : null,
|
|
751
|
+
...(onReceipt === undefined ? {} : { onReceipt }),
|
|
752
|
+
});
|
|
753
|
+
},
|
|
754
|
+
};
|
|
755
|
+
return ops;
|
|
756
|
+
}
|
|
757
|
+
function emptyReceipt(operation) {
|
|
758
|
+
return {
|
|
759
|
+
operation,
|
|
760
|
+
argv: [],
|
|
761
|
+
code: null,
|
|
762
|
+
signal: null,
|
|
763
|
+
spawned: false,
|
|
764
|
+
timed_out: false,
|
|
765
|
+
spawn_error: null,
|
|
766
|
+
stdout: '',
|
|
767
|
+
stderr: '',
|
|
768
|
+
};
|
|
769
|
+
}
|