@skrr-ai/cli 0.1.94 → 0.1.96
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 +175 -165
- package/bin/run.js +32 -0
- package/bin/update-npm-runner.cjs +33 -0
- package/bin/update-session.cjs +194 -0
- package/bin/update-worker.cjs +208 -0
- package/dist/base-command.js +0 -15
- package/dist/commands/agent/message/send.d.ts +2 -0
- package/dist/commands/agent/message/send.js +75 -5
- package/dist/commands/agents/chat.js +1 -1
- package/dist/commands/agents/create.d.ts +0 -3
- package/dist/commands/agents/create.js +0 -26
- package/dist/commands/agents/egress.d.ts +17 -0
- package/dist/commands/agents/egress.js +84 -0
- package/dist/commands/agents/mailbox/messages.d.ts +14 -0
- package/dist/commands/agents/mailbox/messages.js +52 -0
- package/dist/commands/agents/mailbox/show.d.ts +13 -0
- package/dist/commands/agents/mailbox/show.js +52 -0
- package/dist/commands/agents/operating-plan.js +8 -0
- package/dist/commands/agents/roles/capture.d.ts +16 -0
- package/dist/commands/agents/roles/capture.js +53 -0
- package/dist/commands/agents/roles/install.d.ts +1 -0
- package/dist/commands/agents/roles/install.js +8 -1
- package/dist/commands/agents/roles/preview.d.ts +2 -0
- package/dist/commands/agents/roles/preview.js +27 -3
- package/dist/commands/agents/update.d.ts +0 -3
- package/dist/commands/agents/update.js +1 -31
- package/dist/commands/agents/usage-cap.d.ts +4 -1
- package/dist/commands/agents/usage-cap.js +49 -10
- package/dist/commands/code/doctor.d.ts +1 -0
- package/dist/commands/code/doctor.js +6 -0
- package/dist/commands/code/index.js +4 -0
- package/dist/commands/code/install.d.ts +5 -62
- package/dist/commands/code/install.js +86 -279
- package/dist/commands/code/jobs/show.js +12 -0
- package/dist/commands/code/rollback.d.ts +8 -0
- package/dist/commands/code/rollback.js +20 -0
- package/dist/commands/code/update.d.ts +8 -37
- package/dist/commands/code/update.js +134 -213
- package/dist/commands/commitments/action-proposals/decide.d.ts +2 -0
- package/dist/commands/commitments/action-proposals/decide.js +12 -0
- package/dist/commands/commitments/action-proposals.js +7 -0
- package/dist/commands/commitments/create.d.ts +1 -0
- package/dist/commands/commitments/create.js +17 -1
- package/dist/commands/commitments/delivery.d.ts +1 -0
- package/dist/commands/commitments/delivery.js +11 -1
- package/dist/commands/commitments/doctor.js +4 -0
- package/dist/commands/commitments/effective-policy.js +26 -0
- package/dist/commands/commitments/endpoints/create.js +5 -2
- package/dist/commands/commitments/observations.d.ts +10 -0
- package/dist/commands/commitments/observations.js +88 -0
- package/dist/commands/commitments/pause.d.ts +1 -1
- package/dist/commands/commitments/pause.js +4 -3
- package/dist/commands/commitments/preflight.js +2 -1
- package/dist/commands/commitments/reports.d.ts +2 -0
- package/dist/commands/commitments/reports.js +22 -4
- package/dist/commands/commitments/update.d.ts +1 -0
- package/dist/commands/commitments/update.js +35 -5
- package/dist/commands/daemon/install.js +4 -0
- package/dist/commands/generate-recovery-code.d.ts +13 -1
- package/dist/commands/generate-recovery-code.js +146 -13
- package/dist/commands/grants/create.d.ts +1 -0
- package/dist/commands/grants/create.js +8 -0
- package/dist/commands/grants/rotate.d.ts +5 -0
- package/dist/commands/grants/rotate.js +13 -1
- package/dist/commands/grants/update.d.ts +12 -0
- package/dist/commands/grants/update.js +27 -0
- package/dist/commands/instructions/guide.d.ts +20 -0
- package/dist/commands/instructions/guide.js +84 -0
- package/dist/commands/instructions/install.js +57 -33
- package/dist/commands/instructions/setup.d.ts +14 -0
- package/dist/commands/instructions/setup.js +72 -0
- package/dist/commands/integrations/execute.js +1 -1
- package/dist/commands/login.d.ts +5 -6
- package/dist/commands/login.js +27 -11
- package/dist/commands/logout.js +7 -7
- package/dist/commands/machines/hosted/start.js +7 -1
- package/dist/commands/machines/list.js +4 -4
- package/dist/commands/measures/confirm.d.ts +1 -7
- package/dist/commands/measures/confirm.js +15 -14
- package/dist/commands/measures/list.d.ts +1 -7
- package/dist/commands/measures/list.js +1 -7
- package/dist/commands/measures/record.js +4 -4
- package/dist/commands/measures/show.d.ts +11 -0
- package/dist/commands/measures/show.js +26 -0
- package/dist/commands/northstars/governance/configure.d.ts +1 -0
- package/dist/commands/northstars/governance/configure.js +8 -1
- package/dist/commands/proposals/discard.d.ts +5 -0
- package/dist/commands/proposals/discard.js +8 -0
- package/dist/commands/proposals/keep.d.ts +5 -0
- package/dist/commands/proposals/keep.js +8 -0
- package/dist/commands/proposals/list.d.ts +15 -0
- package/dist/commands/proposals/list.js +74 -0
- package/dist/commands/proposals/show.d.ts +12 -0
- package/dist/commands/proposals/show.js +27 -0
- package/dist/commands/proposals/snooze.d.ts +5 -0
- package/dist/commands/proposals/snooze.js +8 -0
- package/dist/commands/security/overview.d.ts +11 -0
- package/dist/commands/security/overview.js +158 -0
- package/dist/commands/signals/monitors/create.d.ts +9 -0
- package/dist/commands/signals/monitors/create.js +29 -0
- package/dist/commands/signals/monitors/list.d.ts +8 -0
- package/dist/commands/signals/monitors/list.js +16 -0
- package/dist/commands/signals/monitors/pause.d.ts +11 -0
- package/dist/commands/signals/monitors/pause.js +17 -0
- package/dist/commands/signals/monitors/resume.d.ts +11 -0
- package/dist/commands/signals/monitors/resume.js +17 -0
- package/dist/commands/signals/monitors/revise.d.ts +12 -0
- package/dist/commands/signals/monitors/revise.js +25 -0
- package/dist/commands/signals/monitors/show.d.ts +11 -0
- package/dist/commands/signals/monitors/show.js +17 -0
- package/dist/commands/signals/monitors/test.d.ts +12 -0
- package/dist/commands/signals/monitors/test.js +25 -0
- package/dist/commands/signals/monitors/transitions.d.ts +11 -0
- package/dist/commands/signals/monitors/transitions.js +17 -0
- package/dist/commands/signals/monitors/validate.d.ts +9 -0
- package/dist/commands/signals/monitors/validate.js +24 -0
- package/dist/commands/signals/schemas/list.d.ts +8 -0
- package/dist/commands/signals/schemas/list.js +16 -0
- package/dist/commands/signals/schemas/show.d.ts +11 -0
- package/dist/commands/signals/schemas/show.js +17 -0
- package/dist/commands/signals/sources/list.d.ts +8 -0
- package/dist/commands/signals/sources/list.js +16 -0
- package/dist/commands/signals/sources/receipts.d.ts +11 -0
- package/dist/commands/signals/sources/receipts.js +17 -0
- package/dist/commands/signals/sources/show.d.ts +11 -0
- package/dist/commands/signals/sources/show.js +17 -0
- package/dist/commands/spaces/claim.d.ts +12 -0
- package/dist/commands/spaces/claim.js +42 -0
- package/dist/commands/spaces/ownership.d.ts +12 -0
- package/dist/commands/spaces/ownership.js +46 -0
- package/dist/commands/spaces/usage.d.ts +12 -0
- package/dist/commands/spaces/usage.js +28 -0
- package/dist/commands/spaces/workflows/run.js +3 -0
- package/dist/commands/spaces/workflows/runs.js +2 -1
- package/dist/commands/spaces/workflows/update.d.ts +6 -1
- package/dist/commands/spaces/workflows/update.js +34 -2
- package/dist/commands/tasks/cancel-cascade.js +1 -1
- package/dist/commands/tasks/cascade-status.d.ts +2 -0
- package/dist/commands/tasks/cascade-status.js +27 -1
- package/dist/commands/tasks/create-tree.d.ts +5 -1
- package/dist/commands/tasks/create-tree.js +35 -7
- package/dist/commands/tasks/create.d.ts +4 -0
- package/dist/commands/tasks/create.js +16 -0
- package/dist/commands/tasks/deliverable/add.js +12 -3
- package/dist/commands/tasks/economics-compare.d.ts +15 -0
- package/dist/commands/tasks/economics-compare.js +60 -0
- package/dist/commands/tasks/expectations/evaluate.d.ts +13 -0
- package/dist/commands/tasks/expectations/evaluate.js +43 -0
- package/dist/commands/tasks/interrupt.js +2 -2
- package/dist/commands/tasks/list.d.ts +1 -0
- package/dist/commands/tasks/list.js +44 -1
- package/dist/commands/tasks/pause-schedule.js +1 -1
- package/dist/commands/tasks/show.d.ts +5 -0
- package/dist/commands/tasks/show.js +17 -0
- package/dist/commands/tasks/update.d.ts +4 -0
- package/dist/commands/tasks/update.js +9 -0
- package/dist/commands/tasks/workflow-findings.js +2 -2
- package/dist/commands/token/list.d.ts +3 -5
- package/dist/commands/token/list.js +99 -57
- package/dist/commands/triggers/create.d.ts +2 -0
- package/dist/commands/triggers/create.js +15 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +61 -0
- package/dist/commands/workspaces/grants.d.ts +12 -0
- package/dist/commands/workspaces/grants.js +31 -0
- package/dist/commands/workspaces/stewardship.d.ts +13 -0
- package/dist/commands/workspaces/stewardship.js +34 -0
- package/dist/generated/managed-instruction-guides.json +9 -0
- package/dist/hooks/init.d.ts +5 -0
- package/dist/hooks/init.js +27 -0
- package/dist/lib/agent-config.d.ts +0 -12
- package/dist/lib/agent-config.js +1 -27
- package/dist/lib/agent-mailbox.d.ts +35 -0
- package/dist/lib/agent-mailbox.js +55 -0
- package/dist/lib/commitment-endpoints.d.ts +2 -1
- package/dist/lib/commitment-endpoints.js +2 -5
- package/dist/lib/commitment-product.d.ts +3 -1
- package/dist/lib/commitment-product.js +10 -0
- package/dist/lib/commitment-terms.js +1 -1
- package/dist/lib/commitments.d.ts +17 -15
- package/dist/lib/commitments.js +173 -137
- package/dist/lib/compasses.d.ts +14 -0
- package/dist/lib/compasses.js +25 -11
- package/dist/lib/daemon-installer.d.ts +2 -0
- package/dist/lib/daemon-installer.js +100 -24
- package/dist/lib/first-party-harness-doctor.js +31 -1
- package/dist/lib/first-party-harness-remote-install.d.ts +71 -0
- package/dist/lib/first-party-harness-remote-install.js +310 -0
- package/dist/lib/first-party-harness-remote-update.d.ts +46 -0
- package/dist/lib/first-party-harness-remote-update.js +242 -0
- package/dist/lib/first-party-harness-startup-update.d.ts +26 -0
- package/dist/lib/first-party-harness-startup-update.js +166 -0
- package/dist/lib/first-party-harness-update.d.ts +46 -0
- package/dist/lib/first-party-harness-update.js +289 -0
- package/dist/lib/first-party-harness.js +7 -4
- package/dist/lib/grants.d.ts +5 -1
- package/dist/lib/grants.js +9 -1
- package/dist/lib/hosted-machines.d.ts +7 -0
- package/dist/lib/mailbox-human-command.d.ts +11 -0
- package/dist/lib/mailbox-human-command.js +41 -0
- package/dist/lib/managed-instruction-local.d.ts +12 -0
- package/dist/lib/managed-instruction-local.js +58 -0
- package/dist/lib/managed-instruction-setup.d.ts +35 -0
- package/dist/lib/managed-instruction-setup.js +306 -0
- package/dist/lib/measures.d.ts +28 -2
- package/dist/lib/measures.js +38 -14
- package/dist/lib/memory-sync.d.ts +1 -0
- package/dist/lib/memory-sync.js +279 -68
- package/dist/lib/message-intent.d.ts +6 -2
- package/dist/lib/message-intent.js +1 -0
- package/dist/lib/northstars.js +2 -1
- package/dist/lib/platform-update-local.d.ts +2 -0
- package/dist/lib/platform-update-local.js +113 -0
- package/dist/lib/platform-update.d.ts +39 -0
- package/dist/lib/platform-update.js +84 -0
- package/dist/lib/profile-machine-identity-guard.d.ts +2 -0
- package/dist/lib/profile-machine-identity-guard.js +13 -0
- package/dist/lib/prompt.d.ts +1 -0
- package/dist/lib/prompt.js +4 -3
- package/dist/lib/proposal-decision-command.d.ts +19 -0
- package/dist/lib/proposal-decision-command.js +64 -0
- package/dist/lib/proposals.d.ts +8 -0
- package/dist/lib/proposals.js +22 -0
- package/dist/lib/roles.d.ts +141 -6
- package/dist/lib/roles.js +8 -2
- package/dist/lib/signals.d.ts +20 -0
- package/dist/lib/signals.js +24 -0
- package/dist/lib/space-usage.d.ts +3 -0
- package/dist/lib/space-usage.js +17 -0
- package/dist/lib/stewardship-output.d.ts +4 -0
- package/dist/lib/stewardship-output.js +82 -0
- package/dist/lib/task-asset-upload.d.ts +3 -1
- package/dist/lib/task-asset-upload.js +1 -0
- package/dist/lib/task-batch.d.ts +9 -0
- package/dist/lib/task-batch.js +94 -17
- package/dist/lib/task-economics.d.ts +16 -0
- package/dist/lib/task-economics.js +22 -0
- package/dist/lib/task-instruction-offer.d.ts +1 -4
- package/dist/lib/task-instruction-offer.js +3 -129
- package/dist/lib/triggers.d.ts +11 -3
- package/dist/lib/triggers.js +58 -1
- package/dist/lib/update-check.d.ts +61 -53
- package/dist/lib/update-check.js +453 -314
- package/dist/lib/usage-discovery.d.ts +10 -0
- package/dist/lib/usage-discovery.js +24 -3
- package/dist/lib/workflow-run-budget.d.ts +12 -0
- package/dist/lib/workflow-run-budget.js +37 -0
- package/dist/lib/workspaces.d.ts +48 -0
- package/dist/lib/workspaces.js +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +14 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.d.ts +82 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.js +105 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.d.ts +110 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.js +945 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/githubPushRefusal.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/githubPushRefusal.js +49 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.js +62 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +35 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.d.ts +63 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.js +169 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.js +44 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.d.ts +82 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.js +102 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.d.ts +110 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.js +923 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/githubPushRefusal.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/githubPushRefusal.js +44 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.js +59 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +35 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.d.ts +63 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.js +163 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.js +41 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +41 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +8069 -7121
- package/docs/v1/README.md +28 -0
- package/docs/v1/api.md +55 -0
- package/docs/v1/changelog.md +16 -0
- package/docs/v1/check-examples.mjs +178 -0
- package/docs/v1/examples/api-readonly.mjs +144 -0
- package/docs/v1/operations.md +87 -0
- package/docs/v1/quickstart.md +71 -0
- package/oclif.manifest.json +49424 -46174
- package/package.json +34 -11
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// This bootstrap uses only Node built-ins: npm may replace the package while
|
|
4
|
+
// the updater runs, so admission and the worker must not depend on its modules.
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const crypto = require('node:crypto');
|
|
9
|
+
const { execFileSync } = require('node:child_process');
|
|
10
|
+
// Protocol 1: bakery admission lock, immutable PID/birth/token leases, and
|
|
11
|
+
// process-group mutation ownership. Its wire/locking contract is immutable.
|
|
12
|
+
// Future protocols must coexist with these sessions or require an external
|
|
13
|
+
// package-manager update; selecting a separate lock must never bypass them.
|
|
14
|
+
const COORDINATION_VERSION = 1;
|
|
15
|
+
|
|
16
|
+
function runtimeDir(packageRoot) {
|
|
17
|
+
const key = crypto
|
|
18
|
+
.createHash('sha256')
|
|
19
|
+
.update(path.resolve(packageRoot))
|
|
20
|
+
.digest('hex')
|
|
21
|
+
.slice(0, 24);
|
|
22
|
+
// Installation leases deliberately ignore SKRR_CONFIG_DIR: two profiles or
|
|
23
|
+
// config roots still share the same npm installation.
|
|
24
|
+
return path.join(os.homedir(), '.skrr', 'cli-update-runtime', key);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readJson(file) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeJson(file, value) {
|
|
36
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
37
|
+
const temporary = `${file}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;
|
|
38
|
+
try {
|
|
39
|
+
fs.writeFileSync(temporary, JSON.stringify(value), { mode: 0o600, flag: 'wx' });
|
|
40
|
+
fs.renameSync(temporary, file);
|
|
41
|
+
} finally {
|
|
42
|
+
fs.rmSync(temporary, { force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function processIdentity(pid) {
|
|
47
|
+
if (process.platform === 'win32') return null;
|
|
48
|
+
try {
|
|
49
|
+
return (
|
|
50
|
+
execFileSync('ps', ['-p', String(pid), '-o', 'lstart='], {
|
|
51
|
+
timeout: 500,
|
|
52
|
+
encoding: 'utf8',
|
|
53
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
54
|
+
}).trim() || null
|
|
55
|
+
);
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isAlive(owner) {
|
|
62
|
+
if (!owner || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) return false;
|
|
63
|
+
try {
|
|
64
|
+
process.kill(owner.group && process.platform !== 'win32' ? -owner.pid : owner.pid, 0);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return error.code !== 'ESRCH';
|
|
67
|
+
}
|
|
68
|
+
// A recycled PID is not the process that created this lease. If the OS
|
|
69
|
+
// cannot disclose its identity, remain conservative rather than expiring a
|
|
70
|
+
// live session on a wall-clock timeout.
|
|
71
|
+
const identity = owner.identity && processIdentity(owner.pid);
|
|
72
|
+
return !identity || identity === owner.identity;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function wait(milliseconds) {
|
|
76
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function acquireLock(directory, timeoutMs = 1000) {
|
|
80
|
+
// Lamport's bakery algorithm, with one immutable participant filename per
|
|
81
|
+
// acquisition. Reaping a crashed participant never removes a replacement
|
|
82
|
+
// owner's lock (the stale-directory removal race of mkdir mutexes).
|
|
83
|
+
const deadline = Date.now() + timeoutMs;
|
|
84
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
85
|
+
const owner = {
|
|
86
|
+
pid: process.pid,
|
|
87
|
+
identity: processIdentity(process.pid),
|
|
88
|
+
token: crypto.randomUUID(),
|
|
89
|
+
};
|
|
90
|
+
const participant = path.join(directory, `${owner.pid}-${owner.token}.json`);
|
|
91
|
+
const participants = () => {
|
|
92
|
+
const active = [];
|
|
93
|
+
for (const file of fs.readdirSync(directory)) {
|
|
94
|
+
if (!file.endsWith('.json')) continue;
|
|
95
|
+
const target = path.join(directory, file);
|
|
96
|
+
const record = readJson(target);
|
|
97
|
+
if (record && isAlive(record)) active.push(record);
|
|
98
|
+
else if (record) fs.rmSync(target, { force: true });
|
|
99
|
+
}
|
|
100
|
+
return active;
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
writeJson(participant, { ...owner, choosing: true, ticket: 0 });
|
|
104
|
+
const ticket = Math.max(0, ...participants().map((record) => record.ticket || 0)) + 1;
|
|
105
|
+
writeJson(participant, { ...owner, choosing: false, ticket });
|
|
106
|
+
while (true) {
|
|
107
|
+
const blocked = participants().some(
|
|
108
|
+
(other) =>
|
|
109
|
+
other.token !== owner.token &&
|
|
110
|
+
(other.choosing ||
|
|
111
|
+
(other.ticket > 0 &&
|
|
112
|
+
(other.ticket < ticket || (other.ticket === ticket && other.token < owner.token)))),
|
|
113
|
+
);
|
|
114
|
+
if (!blocked)
|
|
115
|
+
return () => {
|
|
116
|
+
fs.rmSync(participant, { force: true });
|
|
117
|
+
};
|
|
118
|
+
if (Date.now() >= deadline)
|
|
119
|
+
throw Object.assign(new Error('CLI update lock is busy'), { code: 'CLI_UPDATE_BUSY' });
|
|
120
|
+
wait(25);
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
fs.rmSync(participant, { force: true });
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function liveLeases(directory) {
|
|
129
|
+
const leaseDir = path.join(directory, 'sessions');
|
|
130
|
+
fs.mkdirSync(leaseDir, { recursive: true, mode: 0o700 });
|
|
131
|
+
const live = [];
|
|
132
|
+
for (const file of fs.readdirSync(leaseDir)) {
|
|
133
|
+
const target = path.join(leaseDir, file);
|
|
134
|
+
const lease = readJson(target);
|
|
135
|
+
if (lease && isAlive(lease)) live.push(lease);
|
|
136
|
+
else fs.rmSync(target, { force: true });
|
|
137
|
+
}
|
|
138
|
+
return live;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function registerSession(packageRoot, timeoutMs = 60000) {
|
|
142
|
+
const directory = runtimeDir(packageRoot);
|
|
143
|
+
const deadline = Date.now() + timeoutMs;
|
|
144
|
+
while (true) {
|
|
145
|
+
const release = acquireLock(path.join(directory, 'admission.lock'), 1000);
|
|
146
|
+
try {
|
|
147
|
+
const mutationFile = path.join(directory, 'mutation.json');
|
|
148
|
+
const mutation = readJson(mutationFile);
|
|
149
|
+
if (!mutation || !isAlive(mutation)) {
|
|
150
|
+
if (mutation) fs.rmSync(mutationFile, { force: true });
|
|
151
|
+
const token = crypto.randomUUID();
|
|
152
|
+
const leaseFile = path.join(directory, 'sessions', `${process.pid}-${token}.json`);
|
|
153
|
+
writeJson(leaseFile, {
|
|
154
|
+
pid: process.pid,
|
|
155
|
+
identity: processIdentity(process.pid),
|
|
156
|
+
token,
|
|
157
|
+
coordinationVersion: COORDINATION_VERSION,
|
|
158
|
+
});
|
|
159
|
+
const cleanup = () => {
|
|
160
|
+
try {
|
|
161
|
+
fs.rmSync(leaseFile, { force: true });
|
|
162
|
+
} catch {
|
|
163
|
+
/* read-only home */
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
process.once('exit', cleanup);
|
|
167
|
+
return cleanup;
|
|
168
|
+
}
|
|
169
|
+
} finally {
|
|
170
|
+
release();
|
|
171
|
+
}
|
|
172
|
+
if (Date.now() >= deadline) {
|
|
173
|
+
throw Object.assign(
|
|
174
|
+
new Error(
|
|
175
|
+
'skrr is being updated by npm. Retry shortly; inspect `skrr update --check` after the update finishes.',
|
|
176
|
+
),
|
|
177
|
+
{ code: 'CLI_UPDATE_BUSY' },
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
wait(100);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
COORDINATION_VERSION,
|
|
186
|
+
runtimeDir,
|
|
187
|
+
readJson,
|
|
188
|
+
writeJson,
|
|
189
|
+
processIdentity,
|
|
190
|
+
isAlive,
|
|
191
|
+
acquireLock,
|
|
192
|
+
liveLeases,
|
|
193
|
+
registerSession,
|
|
194
|
+
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Copied next to update-session.cjs outside the npm package before launch.
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const crypto = require('node:crypto');
|
|
7
|
+
const { execFile, spawn } = require('node:child_process');
|
|
8
|
+
const { promisify } = require('node:util');
|
|
9
|
+
const runtime = require('./update-session.cjs');
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
const MAX_WAIT_MS = 5 * 60 * 1000;
|
|
13
|
+
const INSTALL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
14
|
+
const MAX_LOG_BYTES = 256 * 1024;
|
|
15
|
+
|
|
16
|
+
async function run(directory, options = {}) {
|
|
17
|
+
let releaseWorker;
|
|
18
|
+
try {
|
|
19
|
+
releaseWorker = runtime.acquireLock(path.join(directory, 'worker.lock'), 1000);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.code === 'CLI_UPDATE_BUSY') return;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
const stateFile = path.join(directory, 'result.json');
|
|
25
|
+
let mutation = false;
|
|
26
|
+
let mutationChild = null;
|
|
27
|
+
let attempt = runtime.readJson(stateFile);
|
|
28
|
+
const persist = (changes) => {
|
|
29
|
+
attempt = { ...attempt, ...changes, updatedAt: Date.now() };
|
|
30
|
+
runtime.writeJson(stateFile, attempt);
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
if (!attempt || !['queued', 'running'].includes(attempt.status)) return;
|
|
34
|
+
if (
|
|
35
|
+
attempt.coordinationVersion != null &&
|
|
36
|
+
attempt.coordinationVersion !== runtime.COORDINATION_VERSION
|
|
37
|
+
)
|
|
38
|
+
throw new Error(
|
|
39
|
+
'Incompatible CLI update coordination protocol; use the owning package manager',
|
|
40
|
+
);
|
|
41
|
+
if (
|
|
42
|
+
!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(attempt.version) ||
|
|
43
|
+
!path.isAbsolute(attempt.packageRoot) ||
|
|
44
|
+
!path.isAbsolute(attempt.npmPath)
|
|
45
|
+
) {
|
|
46
|
+
throw new Error('Invalid persisted CLI update request');
|
|
47
|
+
}
|
|
48
|
+
// Serialize admission and the empty-session check. Newly starting CLIs
|
|
49
|
+
// then wait before loading oclif/package modules until npm has finished.
|
|
50
|
+
const deadline = Date.now() + (options.maxWaitMs ?? MAX_WAIT_MS);
|
|
51
|
+
while (true) {
|
|
52
|
+
const releaseAdmission = runtime.acquireLock(path.join(directory, 'admission.lock'), 1000);
|
|
53
|
+
try {
|
|
54
|
+
const previousMutation = runtime.readJson(path.join(directory, 'mutation.json'));
|
|
55
|
+
if (
|
|
56
|
+
(!previousMutation || !runtime.isAlive(previousMutation)) &&
|
|
57
|
+
runtime.liveLeases(directory).length === 0
|
|
58
|
+
) {
|
|
59
|
+
runtime.writeJson(path.join(directory, 'mutation.json'), {
|
|
60
|
+
pid: process.pid,
|
|
61
|
+
identity: runtime.processIdentity(process.pid),
|
|
62
|
+
});
|
|
63
|
+
mutation = true;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
} finally {
|
|
67
|
+
releaseAdmission();
|
|
68
|
+
}
|
|
69
|
+
if (Date.now() >= deadline) {
|
|
70
|
+
persist({
|
|
71
|
+
status: 'queued',
|
|
72
|
+
message: 'Waiting for active CLI sessions; the next invocation resumes this request.',
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await delay(250);
|
|
77
|
+
}
|
|
78
|
+
if (
|
|
79
|
+
runtime.readJson(path.join(attempt.packageRoot, 'package.json'))?.version === attempt.version
|
|
80
|
+
) {
|
|
81
|
+
persist({
|
|
82
|
+
status: 'succeeded',
|
|
83
|
+
finishedAt: Date.now(),
|
|
84
|
+
installedVersion: attempt.version,
|
|
85
|
+
message: 'The requested CLI version is already installed.',
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const rootResult = await execFileAsync(attempt.npmPath, ['root', '-g'], {
|
|
90
|
+
timeout: 1500,
|
|
91
|
+
maxBuffer: 64 * 1024,
|
|
92
|
+
shell: process.platform === 'win32',
|
|
93
|
+
});
|
|
94
|
+
const npmRoot = fs.realpathSync(rootResult.stdout.trim());
|
|
95
|
+
const expectedPackage = path.join(npmRoot, '@skrr-ai', 'cli');
|
|
96
|
+
if (fs.realpathSync(expectedPackage) !== fs.realpathSync(attempt.packageRoot)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
'npm global prefix changed; run the update with the package manager that owns this CLI',
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
persist({
|
|
102
|
+
status: 'running',
|
|
103
|
+
startedAt: Date.now(),
|
|
104
|
+
workerPid: process.pid,
|
|
105
|
+
message: 'npm is installing the requested CLI version.',
|
|
106
|
+
});
|
|
107
|
+
const logFile = path.join(directory, 'update.log');
|
|
108
|
+
fs.writeFileSync(logFile, '', { mode: 0o600 });
|
|
109
|
+
let logBytes = 0;
|
|
110
|
+
const log = (chunk) => {
|
|
111
|
+
const bytes = Buffer.from(chunk);
|
|
112
|
+
if (logBytes >= MAX_LOG_BYTES) return;
|
|
113
|
+
const bounded = bytes.subarray(0, MAX_LOG_BYTES - logBytes);
|
|
114
|
+
fs.appendFileSync(logFile, bounded);
|
|
115
|
+
logBytes += bounded.length;
|
|
116
|
+
};
|
|
117
|
+
await new Promise((resolve, reject) => {
|
|
118
|
+
const requestFile = path.join(directory, `runner-${crypto.randomUUID()}.json`);
|
|
119
|
+
const token = crypto.randomUUID();
|
|
120
|
+
runtime.writeJson(requestFile, { token, npmPath: attempt.npmPath, version: attempt.version });
|
|
121
|
+
const child = spawn(
|
|
122
|
+
process.execPath,
|
|
123
|
+
[path.join(__dirname, 'update-npm-runner.cjs'), requestFile],
|
|
124
|
+
{
|
|
125
|
+
detached: process.platform !== 'win32',
|
|
126
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
if (child.pid) {
|
|
130
|
+
mutationChild = { pid: child.pid, group: process.platform !== 'win32' };
|
|
131
|
+
runtime.writeJson(path.join(directory, 'mutation.json'), mutationChild);
|
|
132
|
+
runtime.writeJson(`${requestFile}.go`, { pid: child.pid, token });
|
|
133
|
+
}
|
|
134
|
+
child.stdout.on('data', log);
|
|
135
|
+
child.stderr.on('data', log);
|
|
136
|
+
let timedOut = false;
|
|
137
|
+
let forceTimer;
|
|
138
|
+
const signal = (name) => {
|
|
139
|
+
try {
|
|
140
|
+
if (process.platform !== 'win32' && child.pid) process.kill(-child.pid, name);
|
|
141
|
+
else child.kill(name);
|
|
142
|
+
} catch {
|
|
143
|
+
/* already exited */
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const timer = setTimeout(() => {
|
|
147
|
+
timedOut = true;
|
|
148
|
+
signal('SIGTERM');
|
|
149
|
+
forceTimer = setTimeout(() => {
|
|
150
|
+
signal('SIGKILL');
|
|
151
|
+
}, 5000);
|
|
152
|
+
}, options.installTimeoutMs ?? INSTALL_TIMEOUT_MS);
|
|
153
|
+
child.once('error', (error) => {
|
|
154
|
+
clearTimeout(timer);
|
|
155
|
+
clearTimeout(forceTimer);
|
|
156
|
+
reject(error);
|
|
157
|
+
});
|
|
158
|
+
child.once('exit', async (code, exitSignal) => {
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
if (timedOut) {
|
|
161
|
+
// npm can exit on TERM while a descendant keeps writing. Complete
|
|
162
|
+
// KILL escalation for the whole group before releasing admission.
|
|
163
|
+
signal('SIGKILL');
|
|
164
|
+
const stopDeadline = Date.now() + 5000;
|
|
165
|
+
while (mutationChild && runtime.isAlive(mutationChild) && Date.now() < stopDeadline)
|
|
166
|
+
await delay(50);
|
|
167
|
+
}
|
|
168
|
+
clearTimeout(forceTimer);
|
|
169
|
+
fs.rmSync(requestFile, { force: true });
|
|
170
|
+
fs.rmSync(`${requestFile}.go`, { force: true });
|
|
171
|
+
if (timedOut) reject(new Error('npm update exceeded the 10-minute time limit'));
|
|
172
|
+
else if (code === 0) resolve();
|
|
173
|
+
else reject(new Error(`npm update failed (${exitSignal || code}); see ${logFile}`));
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
const installedVersion = runtime.readJson(
|
|
177
|
+
path.join(attempt.packageRoot, 'package.json'),
|
|
178
|
+
)?.version;
|
|
179
|
+
if (installedVersion !== attempt.version)
|
|
180
|
+
throw new Error(
|
|
181
|
+
`npm completed but installed ${installedVersion || 'an unknown version'} instead of ${attempt.version}`,
|
|
182
|
+
);
|
|
183
|
+
persist({
|
|
184
|
+
status: 'succeeded',
|
|
185
|
+
finishedAt: Date.now(),
|
|
186
|
+
installedVersion,
|
|
187
|
+
message: 'The new CLI version is ready for the next invocation.',
|
|
188
|
+
});
|
|
189
|
+
} catch (error) {
|
|
190
|
+
persist({ status: 'failed', finishedAt: Date.now(), message: String(error.message || error) });
|
|
191
|
+
} finally {
|
|
192
|
+
// A crashed worker/terminated npm parent can leave a mutating process
|
|
193
|
+
// group alive. Keep the gate until the group is confirmed gone.
|
|
194
|
+
if (mutation && (!mutationChild || !runtime.isAlive(mutationChild)))
|
|
195
|
+
fs.rmSync(path.join(directory, 'mutation.json'), { force: true });
|
|
196
|
+
releaseWorker();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (require.main === module) {
|
|
201
|
+
const directory = process.argv[2];
|
|
202
|
+
if (!directory || !path.isAbsolute(directory)) process.exitCode = 1;
|
|
203
|
+
else
|
|
204
|
+
run(directory).catch(() => {
|
|
205
|
+
process.exitCode = 1;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
module.exports = { run };
|
package/dist/base-command.js
CHANGED
|
@@ -52,7 +52,6 @@ const daemonBroker_1 = require("./lib/daemonBroker");
|
|
|
52
52
|
const daemonBrokerRefusal_1 = require("./lib/daemonBrokerRefusal");
|
|
53
53
|
const refresh_1 = require("./lib/refresh");
|
|
54
54
|
const auth_storage_1 = require("./lib/auth-storage");
|
|
55
|
-
const update_check_1 = require("./lib/update-check");
|
|
56
55
|
const web_url_1 = require("./lib/web-url");
|
|
57
56
|
const edge_error_page_1 = require("./lib/edge-error-page");
|
|
58
57
|
const step_up_1 = require("./lib/step-up");
|
|
@@ -541,20 +540,6 @@ class BaseCommand extends core_1.Command {
|
|
|
541
540
|
}
|
|
542
541
|
(0, node_adapter_1.setAdapterCredentialOverride)(this.resolvedCredential);
|
|
543
542
|
(0, data_provider_1.setHttpAdapter)((0, node_adapter_1.createNodeAdapter)());
|
|
544
|
-
// Non-blocking "a newer skrr is available" advisory. Decides from a ~4h
|
|
545
|
-
// on-disk cache (zero added latency) and refreshes in the background.
|
|
546
|
-
// Writes at most one line to stderr, never stdout, so --json consumers
|
|
547
|
-
// are unaffected; self-suppresses in bundled/non-TTY/opted-out contexts.
|
|
548
|
-
// Wrapped so an advisory can never break the command it precedes.
|
|
549
|
-
try {
|
|
550
|
-
(0, update_check_1.maybeWarnOutdatedCli)({
|
|
551
|
-
currentVersion: this.config.version,
|
|
552
|
-
baseURL: this.cliConfig.baseURL,
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
catch {
|
|
556
|
-
/* advisory is strictly best-effort */
|
|
557
|
-
}
|
|
558
543
|
}
|
|
559
544
|
/**
|
|
560
545
|
* Read only the two global base flags. A narrow parser — oclif's
|
|
@@ -85,6 +85,8 @@ export default class AgentMessageSend extends BaseCommand {
|
|
|
85
85
|
part: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
86
86
|
'message-lint': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
87
87
|
destination: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
88
|
+
to: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
89
|
+
subject: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
88
90
|
channel: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
89
91
|
'thread-ts': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
90
92
|
'thread-root': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -114,6 +114,11 @@ class AgentMessageSend extends base_command_1.BaseCommand {
|
|
|
114
114
|
description: 'Destination kind for the canonical intent',
|
|
115
115
|
options: [...message_intent_1.CANONICAL_DESTINATION_KINDS],
|
|
116
116
|
}),
|
|
117
|
+
to: core_1.Flags.string({
|
|
118
|
+
description: 'Recipient email address; repeat for up to 5 recipients',
|
|
119
|
+
multiple: true,
|
|
120
|
+
}),
|
|
121
|
+
subject: core_1.Flags.string({ description: 'Plain email subject (agent_email only)' }),
|
|
117
122
|
channel: core_1.Flags.string({
|
|
118
123
|
description: 'Slack channel ID (only meaningful with --destination slack_channel)',
|
|
119
124
|
}),
|
|
@@ -178,6 +183,8 @@ class AgentMessageSend extends base_command_1.BaseCommand {
|
|
|
178
183
|
agentId: flags.agent,
|
|
179
184
|
text: flags.text,
|
|
180
185
|
destinationKind: flags.destination,
|
|
186
|
+
recipients: flags.to,
|
|
187
|
+
subject: flags.subject,
|
|
181
188
|
channelId: flags.channel,
|
|
182
189
|
threadTs: flags['thread-ts'],
|
|
183
190
|
threadRootMessageId: flags['thread-root'],
|
|
@@ -497,6 +504,9 @@ class AgentMessageSend extends base_command_1.BaseCommand {
|
|
|
497
504
|
},
|
|
498
505
|
});
|
|
499
506
|
}
|
|
507
|
+
if (request.destination.kind === 'agent_email') {
|
|
508
|
+
this.error('Agent email is available only through an authorized Agent session.');
|
|
509
|
+
}
|
|
500
510
|
// slack_owner_dm is a Gateway-only destination: it applies workspace-scoped
|
|
501
511
|
// owner routing that no legacy human transport can enact.
|
|
502
512
|
this.failWithCliError({
|
|
@@ -538,6 +548,14 @@ function messageSegmentationAdvisory({ destination, text, followingParts, }) {
|
|
|
538
548
|
* from one explicit semantic idempotency key.
|
|
539
549
|
*/
|
|
540
550
|
function buildSequencedRequests(request, followingParts, hasExplicitIdempotencyKey) {
|
|
551
|
+
if (request.destination.kind === 'agent_email') {
|
|
552
|
+
if (followingParts.length)
|
|
553
|
+
throw invalidIntent('Email accepts one plain-text body; --part is not supported.');
|
|
554
|
+
requireText(request.content.text, 'content.text', 20 * 1024);
|
|
555
|
+
if (Buffer.byteLength(request.content.text, 'utf8') > 20 * 1024)
|
|
556
|
+
throw invalidIntent('Email body exceeds 20 KiB.');
|
|
557
|
+
return [request];
|
|
558
|
+
}
|
|
541
559
|
const parts = [request.content.text, ...followingParts].map((part) => requireText(part, 'part'));
|
|
542
560
|
if (parts.length === 1)
|
|
543
561
|
return [request];
|
|
@@ -586,6 +604,8 @@ const DELIVERY_FAILURE_STATUSES = new Set([
|
|
|
586
604
|
'dropped',
|
|
587
605
|
'expired',
|
|
588
606
|
'cancelled',
|
|
607
|
+
'unknown',
|
|
608
|
+
'refused',
|
|
589
609
|
]);
|
|
590
610
|
/**
|
|
591
611
|
* Exit codes for a submitted-but-undelivered message.
|
|
@@ -723,8 +743,18 @@ function normalizeIntentPayload({ agentId, jsonBody, merged, }) {
|
|
|
723
743
|
bodyDestination,
|
|
724
744
|
overlayChannel,
|
|
725
745
|
overlayThreadTs,
|
|
746
|
+
overlayRecipients: merged.recipients,
|
|
747
|
+
overlaySubject: merged.subject,
|
|
726
748
|
});
|
|
727
|
-
const text = requireText(readTextField(jsonBody, merged), 'content.text');
|
|
749
|
+
const text = requireText(readTextField(jsonBody, merged), 'content.text', destination.kind === 'agent_email' ? 20 * 1024 : MAX_TEXT_CHARS);
|
|
750
|
+
if (destination.kind === 'agent_email') {
|
|
751
|
+
if (Buffer.byteLength(text, 'utf8') > 20 * 1024)
|
|
752
|
+
throw invalidIntent('Email body exceeds 20 KiB.');
|
|
753
|
+
const forbidden = ['from', 'cc', 'bcc', 'html', 'attachments', 'headers', 'replyTo'];
|
|
754
|
+
const content = jsonBody.content;
|
|
755
|
+
if (forbidden.some((key) => jsonBody[key] !== undefined || content?.[key] !== undefined))
|
|
756
|
+
throw invalidIntent('Agent email accepts plain text only; sender and headers are server-owned.');
|
|
757
|
+
}
|
|
728
758
|
const idempotencyKey = optionalIdempotencyKey(merged.idempotencyKey ??
|
|
729
759
|
jsonBody.idempotencyKey ??
|
|
730
760
|
jsonBody['idempotency_key'] ??
|
|
@@ -778,10 +808,38 @@ function pickDestinationKind({ bodyDestination, overlayKind, }) {
|
|
|
778
808
|
}
|
|
779
809
|
throw invalidIntent(`destination.kind must be one of ${message_intent_1.CANONICAL_DESTINATION_KINDS.join(', ')} (got "${normalized}").`);
|
|
780
810
|
}
|
|
781
|
-
function buildDestination({ kind, bodyDestination, overlayChannel, overlayThreadTs, }) {
|
|
811
|
+
function buildDestination({ kind, bodyDestination, overlayChannel, overlayThreadTs, overlayRecipients, overlaySubject, }) {
|
|
782
812
|
if (kind === 'app_owner_dm' || kind === 'slack_owner_dm') {
|
|
783
813
|
return { kind };
|
|
784
814
|
}
|
|
815
|
+
if (kind === 'agent_email') {
|
|
816
|
+
const source = bodyDestination;
|
|
817
|
+
if (source &&
|
|
818
|
+
Object.keys(source).some((key) => !['kind', 'recipients', 'subject'].includes(key))) {
|
|
819
|
+
throw invalidIntent('agent_email accepts only recipients and subject.');
|
|
820
|
+
}
|
|
821
|
+
const rawRecipients = overlayRecipients ?? source?.recipients;
|
|
822
|
+
if (!Array.isArray(rawRecipients) || rawRecipients.length < 1 || rawRecipients.length > 5) {
|
|
823
|
+
throw invalidIntent('agent_email requires 1 to 5 recipients.');
|
|
824
|
+
}
|
|
825
|
+
const recipients = rawRecipients.map((value) => {
|
|
826
|
+
if (typeof value !== 'string' ||
|
|
827
|
+
value.length > 254 ||
|
|
828
|
+
!/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(value))
|
|
829
|
+
throw invalidIntent('Invalid email recipient.');
|
|
830
|
+
return value.toLowerCase();
|
|
831
|
+
});
|
|
832
|
+
if (new Set(recipients).size !== recipients.length)
|
|
833
|
+
throw invalidIntent('Email recipients must be distinct.');
|
|
834
|
+
const subject = overlaySubject ?? source?.subject;
|
|
835
|
+
if (typeof subject !== 'string' ||
|
|
836
|
+
!subject.trim() ||
|
|
837
|
+
subject.length > 200 ||
|
|
838
|
+
// eslint-disable-next-line no-control-regex -- Reject control characters in mail headers.
|
|
839
|
+
/[\r\n\x00-\x1f\x7f]/.test(subject))
|
|
840
|
+
throw invalidIntent('Email subject must be a single line of at most 200 characters.');
|
|
841
|
+
return { kind, recipients: recipients.sort(), subject: subject.trim() };
|
|
842
|
+
}
|
|
785
843
|
const channelId = optionalString(overlayChannel ?? bodyDestination?.channelId);
|
|
786
844
|
if (!channelId) {
|
|
787
845
|
throw invalidIntent('slack_channel destinations require destination.channelId.');
|
|
@@ -905,6 +963,16 @@ function booleanFlagInput(argv, name, parsedValue) {
|
|
|
905
963
|
}
|
|
906
964
|
function canonicalIntentFromDestination({ agentId, idempotencyKey, destination, text, presentation, notificationPolicy, context, threadRootMessageId, embeds, }) {
|
|
907
965
|
switch (destination.kind) {
|
|
966
|
+
case 'agent_email':
|
|
967
|
+
return {
|
|
968
|
+
protocolVersion: 1,
|
|
969
|
+
agentId,
|
|
970
|
+
intentType: 'status_update',
|
|
971
|
+
destination,
|
|
972
|
+
content: { text },
|
|
973
|
+
idempotencyKey,
|
|
974
|
+
...(context ? { context } : {}),
|
|
975
|
+
};
|
|
908
976
|
case 'app_owner_dm':
|
|
909
977
|
return (0, message_intent_1.canonicalAppOwnerDmRequest)({
|
|
910
978
|
agentId,
|
|
@@ -947,13 +1015,13 @@ function resolveAgentInput(argValue, flagValue, merged, jsonBody) {
|
|
|
947
1015
|
{ label: 'agentId in JSON input', value: fromBody ?? fromMerged },
|
|
948
1016
|
]);
|
|
949
1017
|
}
|
|
950
|
-
function requireText(value, field) {
|
|
1018
|
+
function requireText(value, field, maxChars = MAX_TEXT_CHARS) {
|
|
951
1019
|
if (typeof value !== 'string' || !value.trim()) {
|
|
952
1020
|
throw invalidIntent(`${field} is required.`);
|
|
953
1021
|
}
|
|
954
1022
|
const normalized = value.trim();
|
|
955
|
-
if (normalized.length >
|
|
956
|
-
throw invalidIntent(`${field} must be ${
|
|
1023
|
+
if (normalized.length > maxChars) {
|
|
1024
|
+
throw invalidIntent(`${field} must be ${maxChars} characters or less.`);
|
|
957
1025
|
}
|
|
958
1026
|
return normalized;
|
|
959
1027
|
}
|
|
@@ -1043,6 +1111,8 @@ function formatEnvelope(envelope) {
|
|
|
1043
1111
|
return `Agent message intent for ${envelope.agent.id} → ${destination}: ${status}${suffix}.${presentation}`;
|
|
1044
1112
|
}
|
|
1045
1113
|
function describeDestination(destination) {
|
|
1114
|
+
if (destination.kind === 'agent_email')
|
|
1115
|
+
return `agent_email:${destination.recipients.join(', ')}`;
|
|
1046
1116
|
if (destination.kind === 'slack_channel') {
|
|
1047
1117
|
return `slack_channel:${destination.channelId}${destination.threadTs ? `:${destination.threadTs}` : ''}`;
|
|
1048
1118
|
}
|
|
@@ -594,7 +594,7 @@ class AgentsChat extends base_command_1.BaseCommand {
|
|
|
594
594
|
'status is not managed for you. The runtime session is bound to this task for ' +
|
|
595
595
|
'task context and artifact uploads; use the canonical id above.',
|
|
596
596
|
].join('\n');
|
|
597
|
-
if (!flags.jsonl) {
|
|
597
|
+
if (!flags.jsonl && !flags.json) {
|
|
598
598
|
this.log(`Bound to task ${task.identifier ?? taskId} (chat turn — task not checked out)`);
|
|
599
599
|
}
|
|
600
600
|
}
|
|
@@ -124,9 +124,6 @@ export default class AgentsCreate extends BaseCommand {
|
|
|
124
124
|
avatar: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
125
125
|
'conversation-starter': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
126
126
|
'todo-working-hours': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
127
|
-
'reasoning-tool-escape': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
128
|
-
'commitment-responsibility': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
129
|
-
'commitment-responsibility-ceiling': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
130
127
|
'max-turns': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
131
128
|
'tool-resources': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
132
129
|
'install-channel': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -280,32 +280,6 @@ class AgentsCreate extends base_command_1.BaseCommand {
|
|
|
280
280
|
description: 'When this agent schedules its own work: HH:MM-HH:MM[@<IANA timezone>], e.g. ' +
|
|
281
281
|
'09:00-18:00@America/Los_Angeles',
|
|
282
282
|
}),
|
|
283
|
-
// Retired (OSK-11975): a Commitment turn works under the Agent's own
|
|
284
|
-
// permissions. Hidden, warned on use, and never sent (OSK-12068) — see
|
|
285
|
-
// `mergeAgentConfigFlags`. Kept parseable so an old script still runs.
|
|
286
|
-
'reasoning-tool-escape': core_1.Flags.string({
|
|
287
|
-
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
288
|
-
options: [...agent_config_1.REASONING_TOOL_ESCAPES],
|
|
289
|
-
multiple: true,
|
|
290
|
-
deprecated: { message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('reasoning-tool-escape') },
|
|
291
|
-
hidden: true,
|
|
292
|
-
}),
|
|
293
|
-
'commitment-responsibility': core_1.Flags.string({
|
|
294
|
-
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
295
|
-
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
296
|
-
deprecated: {
|
|
297
|
-
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility'),
|
|
298
|
-
},
|
|
299
|
-
hidden: true,
|
|
300
|
-
}),
|
|
301
|
-
'commitment-responsibility-ceiling': core_1.Flags.string({
|
|
302
|
-
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
303
|
-
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
304
|
-
deprecated: {
|
|
305
|
-
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility-ceiling'),
|
|
306
|
-
},
|
|
307
|
-
hidden: true,
|
|
308
|
-
}),
|
|
309
283
|
'max-turns': core_1.Flags.integer({ description: 'Maximum turns in one agent run (1-500)' }),
|
|
310
284
|
'tool-resources': core_1.Flags.string({
|
|
311
285
|
description: 'Tool resources JSON object (file_search / context / image_edit bindings)',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
export default class AgentsEgress extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static flags: {
|
|
8
|
+
allow: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
remove: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
repo: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
'remove-repo': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
reason: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
|
+
clear: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|