@skrr-ai/cli 0.1.48 → 0.1.50
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 +5 -4
- package/dist/base-command.js +16 -4
- package/dist/commands/agent-worker.d.ts +12 -0
- package/dist/commands/agent-worker.js +140 -0
- package/dist/commands/agents/create.js +14 -7
- package/dist/commands/agents/update.js +11 -5
- package/dist/commands/code/index.d.ts +1 -0
- package/dist/commands/code/index.js +49 -5
- package/dist/commands/code/run.js +5 -1
- package/dist/commands/commitments/cycles.d.ts +19 -0
- package/dist/commands/commitments/cycles.js +46 -0
- package/dist/commands/commitments/effective-policy.js +26 -1
- package/dist/commands/commitments/explain.d.ts +17 -0
- package/dist/commands/commitments/explain.js +40 -0
- package/dist/commands/goals/create.d.ts +1 -0
- package/dist/commands/goals/create.js +22 -0
- package/dist/commands/logout.js +12 -1
- package/dist/commands/machines/dedicated/index.js +1 -1
- package/dist/commands/machines/dedicated/sign-in.js +4 -1
- package/dist/commands/machines/dedicated/terminal/kill.d.ts +21 -0
- package/dist/commands/machines/dedicated/terminal/kill.js +55 -0
- package/dist/commands/machines/dedicated/terminal/ls.d.ts +22 -0
- package/dist/commands/machines/dedicated/terminal/ls.js +70 -0
- package/dist/commands/machines/dedicated/terminal/rename.d.ts +23 -0
- package/dist/commands/machines/dedicated/terminal/rename.js +64 -0
- package/dist/commands/machines/dedicated/terminal.d.ts +9 -0
- package/dist/commands/machines/dedicated/terminal.js +27 -2
- package/dist/commands/machines/hosted/connect.js +2 -2
- package/dist/commands/machines/hosted/destroy.js +1 -1
- package/dist/commands/machines/hosted/exec.js +1 -1
- package/dist/commands/machines/hosted/list.js +1 -1
- package/dist/commands/machines/hosted/pause.js +1 -1
- package/dist/commands/machines/hosted/pull.js +1 -1
- package/dist/commands/machines/hosted/resume.js +1 -1
- package/dist/commands/machines/hosted/start.js +2 -2
- package/dist/commands/machines/hosted/status.js +2 -1
- package/dist/commands/tasks/create.js +1 -0
- package/dist/commands/tasks/list.js +1 -0
- package/dist/commands/tasks/update.js +1 -0
- package/dist/lib/agent-config.d.ts +2 -0
- package/dist/lib/agent-config.js +3 -1
- package/dist/lib/api-fetch.js +19 -0
- package/dist/lib/auth-storage.d.ts +14 -0
- package/dist/lib/auth-storage.js +14 -0
- package/dist/lib/commitments.d.ts +17 -2
- package/dist/lib/commitments.js +114 -0
- package/dist/lib/daemon-installer.d.ts +6 -5
- package/dist/lib/daemon-installer.js +4 -17
- package/dist/lib/daemonBroker.d.ts +120 -31
- package/dist/lib/daemonBroker.js +313 -20
- package/dist/lib/dedicated-lease-command.d.ts +14 -0
- package/dist/lib/dedicated-lease-command.js +30 -1
- package/dist/lib/dedicated-machines.js +8 -25
- package/dist/lib/dedicated-service-command.d.ts +11 -3
- package/dist/lib/dedicated-service-command.js +20 -3
- package/dist/lib/dedicated-service.d.ts +46 -0
- package/dist/lib/dedicated-service.js +85 -7
- package/dist/lib/dedicated-terminal.d.ts +21 -0
- package/dist/lib/dedicated-terminal.js +104 -9
- package/dist/lib/first-party-harness-broker.d.ts +10 -0
- package/dist/lib/first-party-harness-broker.js +9 -0
- package/dist/lib/first-party-harness-doctor.js +41 -1
- package/dist/lib/first-party-harness-managed.d.ts +4 -3
- package/dist/lib/first-party-harness-project-trust.d.ts +125 -0
- package/dist/lib/first-party-harness-project-trust.js +364 -0
- package/dist/lib/first-party-harness.d.ts +9 -2
- package/dist/lib/first-party-harness.js +6 -5
- package/dist/lib/hosted-machines.d.ts +10 -1
- package/dist/lib/hosted-machines.js +36 -2
- package/dist/lib/login.js +16 -0
- package/dist/lib/machine-spend-cap.d.ts +23 -0
- package/dist/lib/machine-spend-cap.js +68 -0
- package/dist/lib/node-adapter.js +15 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +90 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +113 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +47 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +69 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +109 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +171 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +8 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +9 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.d.ts +6 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.js +32 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.d.ts +38 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.js +66 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.d.ts +81 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.js +87 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +6 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +90 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +105 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +47 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +66 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +109 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +164 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +8 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +9 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.d.ts +6 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.js +31 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.d.ts +38 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.js +64 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.d.ts +81 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.js +80 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +6 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +41 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3413 -3343
- package/oclif.manifest.json +8930 -8466
- package/package.json +4 -1
|
@@ -43,6 +43,31 @@ export declare const DEDICATED_SERVICE_EVENTS: {
|
|
|
43
43
|
readonly logClosed: "dedicated:service:log:closed";
|
|
44
44
|
readonly proposed: "dedicated:service:proposed";
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Persistent-terminal management (always-on §6.4) — the sibling lane on the
|
|
48
|
+
* same admitted socket. A session is a tmux `skrr-term-<name>` the machine
|
|
49
|
+
* keeps alive across attaches; `terminal` opens one over the ordinary
|
|
50
|
+
* terminal start lane, and these verbs enumerate, end and rename it.
|
|
51
|
+
*/
|
|
52
|
+
export declare const DEDICATED_TERMINAL_EVENTS: {
|
|
53
|
+
readonly list: "dedicated:terminal:list";
|
|
54
|
+
readonly kill: "dedicated:terminal:kill";
|
|
55
|
+
readonly rename: "dedicated:terminal:rename";
|
|
56
|
+
readonly ack: "dedicated:terminal:ack";
|
|
57
|
+
};
|
|
58
|
+
export type TerminalVerb = 'list' | 'kill' | 'rename';
|
|
59
|
+
/** `skrr-term-<name>`'s suffix — the pattern the relay and daemon both enforce. */
|
|
60
|
+
export declare const DEDICATED_TERMINAL_NAME_PATTERN: RegExp;
|
|
61
|
+
/** The wire capability a live daemon must announce for persistent terminals. */
|
|
62
|
+
export declare const DEDICATED_PERSISTENT_TERMINAL_CAPABILITY = "persistent_terminal_v1";
|
|
63
|
+
/** One `dedicated:terminal:list` row — the tmux session under its suffix. */
|
|
64
|
+
export interface DedicatedTerminalSessionInfo {
|
|
65
|
+
name: string;
|
|
66
|
+
/** tmux `session_created` — SECONDS when numeric, not milliseconds. */
|
|
67
|
+
createdAt?: string | number;
|
|
68
|
+
attached?: boolean;
|
|
69
|
+
windows?: number;
|
|
70
|
+
}
|
|
46
71
|
/** How long a verb has to answer before the machine is declared unreachable. */
|
|
47
72
|
export declare const SERVICE_CALL_TIMEOUT_MS = 30000;
|
|
48
73
|
/** §5.3 — the scope name `skrr-svc-<name>` derives from it. */
|
|
@@ -167,6 +192,17 @@ export declare function describeServiceRefusal(ack: Pick<DedicatedServiceAck, 'c
|
|
|
167
192
|
verb?: string;
|
|
168
193
|
name?: string;
|
|
169
194
|
}): string;
|
|
195
|
+
/**
|
|
196
|
+
* A persistent-terminal refusal in the same register `describeServiceRefusal`
|
|
197
|
+
* gives the service lane — a named condition and the next step, never a bare
|
|
198
|
+
* code. `dedicated:terminal:*` acks share this channel's requestId shape, so
|
|
199
|
+
* the same refusal codes arrive; the words name terminal sessions, not specs.
|
|
200
|
+
*/
|
|
201
|
+
export declare function describeTerminalRefusal(ack: Pick<DedicatedServiceAck, 'code' | 'error'>, context?: {
|
|
202
|
+
leaseId?: string;
|
|
203
|
+
verb?: string;
|
|
204
|
+
name?: string;
|
|
205
|
+
}): string;
|
|
170
206
|
/** Validation that costs the daemon nothing to re-check — §5.3's table, enforced client-side first. */
|
|
171
207
|
export declare function validateServiceSpec(doc: unknown, options?: {
|
|
172
208
|
requireName?: boolean;
|
|
@@ -251,6 +287,16 @@ export declare class DedicatedServiceChannel {
|
|
|
251
287
|
result: T | undefined;
|
|
252
288
|
ack: DedicatedServiceAck;
|
|
253
289
|
}>;
|
|
290
|
+
/**
|
|
291
|
+
* A persistent-terminal verb — `dedicated:terminal:list|kill|rename`,
|
|
292
|
+
* answered on `dedicated:terminal:ack`. Same requestId/ack discipline as
|
|
293
|
+
* the service verbs; the event family and the refusal's wording are the
|
|
294
|
+
* only differences.
|
|
295
|
+
*/
|
|
296
|
+
callTerminal<T = unknown>(verb: TerminalVerb, payload: Record<string, unknown>, timeoutMs?: number): Promise<{
|
|
297
|
+
result: T | undefined;
|
|
298
|
+
ack: DedicatedServiceAck;
|
|
299
|
+
}>;
|
|
254
300
|
/** The shared request/ack round-trip. */
|
|
255
301
|
private callEvent;
|
|
256
302
|
/** Subscribe to a server-pushed frame (`state`, `log`, `log:closed`, `proposed`). */
|
|
@@ -3,9 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.GUEST_SERVICE_SOCKET_PATH = exports.DedicatedServiceChannel = exports.DedicatedServiceError = exports.SERVICE_ACTIONS = exports.SERVICE_RESTART_POLICIES = exports.SERVICE_RESERVED_NAMES = exports.SERVICE_SECRET_NAME_PATTERN = exports.SERVICE_NAME_PATTERN = exports.SERVICE_CALL_TIMEOUT_MS = exports.DEDICATED_SERVICE_EVENTS = void 0;
|
|
6
|
+
exports.GUEST_SERVICE_SOCKET_PATH = exports.DedicatedServiceChannel = exports.DedicatedServiceError = exports.SERVICE_ACTIONS = exports.SERVICE_RESTART_POLICIES = exports.SERVICE_RESERVED_NAMES = exports.SERVICE_SECRET_NAME_PATTERN = exports.SERVICE_NAME_PATTERN = exports.SERVICE_CALL_TIMEOUT_MS = exports.DEDICATED_PERSISTENT_TERMINAL_CAPABILITY = exports.DEDICATED_TERMINAL_NAME_PATTERN = exports.DEDICATED_TERMINAL_EVENTS = exports.DEDICATED_SERVICE_EVENTS = void 0;
|
|
7
7
|
exports.isServiceUnsupportedCode = isServiceUnsupportedCode;
|
|
8
8
|
exports.describeServiceRefusal = describeServiceRefusal;
|
|
9
|
+
exports.describeTerminalRefusal = describeTerminalRefusal;
|
|
9
10
|
exports.validateServiceSpec = validateServiceSpec;
|
|
10
11
|
exports.parseServiceSpecDocument = parseServiceSpecDocument;
|
|
11
12
|
exports.specPayload = specPayload;
|
|
@@ -72,6 +73,22 @@ exports.DEDICATED_SERVICE_EVENTS = {
|
|
|
72
73
|
logClosed: 'dedicated:service:log:closed',
|
|
73
74
|
proposed: 'dedicated:service:proposed',
|
|
74
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Persistent-terminal management (always-on §6.4) — the sibling lane on the
|
|
78
|
+
* same admitted socket. A session is a tmux `skrr-term-<name>` the machine
|
|
79
|
+
* keeps alive across attaches; `terminal` opens one over the ordinary
|
|
80
|
+
* terminal start lane, and these verbs enumerate, end and rename it.
|
|
81
|
+
*/
|
|
82
|
+
exports.DEDICATED_TERMINAL_EVENTS = {
|
|
83
|
+
list: 'dedicated:terminal:list',
|
|
84
|
+
kill: 'dedicated:terminal:kill',
|
|
85
|
+
rename: 'dedicated:terminal:rename',
|
|
86
|
+
ack: 'dedicated:terminal:ack',
|
|
87
|
+
};
|
|
88
|
+
/** `skrr-term-<name>`'s suffix — the pattern the relay and daemon both enforce. */
|
|
89
|
+
exports.DEDICATED_TERMINAL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
90
|
+
/** The wire capability a live daemon must announce for persistent terminals. */
|
|
91
|
+
exports.DEDICATED_PERSISTENT_TERMINAL_CAPABILITY = 'persistent_terminal_v1';
|
|
75
92
|
/** How long a verb has to answer before the machine is declared unreachable. */
|
|
76
93
|
exports.SERVICE_CALL_TIMEOUT_MS = 30_000;
|
|
77
94
|
/** §5.3 — the scope name `skrr-svc-<name>` derives from it. */
|
|
@@ -106,6 +123,7 @@ exports.DedicatedServiceError = DedicatedServiceError;
|
|
|
106
123
|
function isServiceUnsupportedCode(code) {
|
|
107
124
|
const c = (code || '').toLowerCase();
|
|
108
125
|
return (c === 'dedicated_runtime_services_unsupported' ||
|
|
126
|
+
c === 'dedicated_runtime_terminal_unsupported' ||
|
|
109
127
|
c === 'services_unsupported' ||
|
|
110
128
|
c === 'unsupported' ||
|
|
111
129
|
c === 'not_implemented' ||
|
|
@@ -152,6 +170,48 @@ function describeServiceRefusal(ack, context = {}) {
|
|
|
152
170
|
}
|
|
153
171
|
return ack.error ? `The service request failed: ${ack.error}` : 'The service request failed.';
|
|
154
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* A persistent-terminal refusal in the same register `describeServiceRefusal`
|
|
175
|
+
* gives the service lane — a named condition and the next step, never a bare
|
|
176
|
+
* code. `dedicated:terminal:*` acks share this channel's requestId shape, so
|
|
177
|
+
* the same refusal codes arrive; the words name terminal sessions, not specs.
|
|
178
|
+
*/
|
|
179
|
+
function describeTerminalRefusal(ack, context = {}) {
|
|
180
|
+
const code = ack.code || '';
|
|
181
|
+
if (isServiceUnsupportedCode(code)) {
|
|
182
|
+
return (ack.error ||
|
|
183
|
+
`This machine's image does not support persistent terminals (persistent_terminal_v1). ` +
|
|
184
|
+
`Update it with \`skrr machines dedicated update-image ${context.leaseId || '<lease-id>'} --wait\`; \`skrr machines dedicated terminal\` opens a plain shell in the meantime.`);
|
|
185
|
+
}
|
|
186
|
+
if (code === 'DEDICATED_RUNTIME_NOT_ASSIGNABLE') {
|
|
187
|
+
return ('The machine is not ready to take terminal commands (it must be running with a healthy workspace). ' +
|
|
188
|
+
`Check it with \`skrr machines dedicated show ${context.leaseId || '<lease-id>'}\`.`);
|
|
189
|
+
}
|
|
190
|
+
if (code === 'DEDICATED_RUNTIME_GENERATION_STALE') {
|
|
191
|
+
return 'The machine was replaced since its daemon registered. Wait a minute and try again.';
|
|
192
|
+
}
|
|
193
|
+
if (code === 'dedicated_runtime_quiesced') {
|
|
194
|
+
return 'The machine is checkpointing its workspace right now; try again in a moment.';
|
|
195
|
+
}
|
|
196
|
+
if (code === 'offline' ||
|
|
197
|
+
code === 'daemon_offline' ||
|
|
198
|
+
code === 'daemon-gone' ||
|
|
199
|
+
code === 'daemon-timeout') {
|
|
200
|
+
return 'The machine’s daemon is not answering. Wait for it to come online and try again.';
|
|
201
|
+
}
|
|
202
|
+
if (code === 'not_found') {
|
|
203
|
+
return context.name
|
|
204
|
+
? `No terminal session named ${context.name} on this machine — \`skrr machines dedicated terminal ls ${context.leaseId || '<lease-id>'}\` lists them.`
|
|
205
|
+
: 'The machine or the named session was not found.';
|
|
206
|
+
}
|
|
207
|
+
if (code === 'invalid' || code === 'validation') {
|
|
208
|
+
return `The terminal request was refused: ${ack.error || 'invalid request'}`;
|
|
209
|
+
}
|
|
210
|
+
if (code === 'rate-limited' || code === 'cap') {
|
|
211
|
+
return 'Too many terminal requests in flight — wait a moment and retry.';
|
|
212
|
+
}
|
|
213
|
+
return ack.error ? `The terminal request failed: ${ack.error}` : 'The terminal request failed.';
|
|
214
|
+
}
|
|
155
215
|
/* ---------------------------------------------------------------- spec --- */
|
|
156
216
|
/** Validation that costs the daemon nothing to re-check — §5.3's table, enforced client-side first. */
|
|
157
217
|
function validateServiceSpec(doc, options = {}) {
|
|
@@ -383,18 +443,22 @@ class DedicatedServiceChannel {
|
|
|
383
443
|
this.options = options;
|
|
384
444
|
const connect = options.connect ?? socket_io_client_1.io;
|
|
385
445
|
this.socket = connect((0, agentic_stream_1.toWebSocketOrigin)(options.baseURL), socketOptions(options.token));
|
|
386
|
-
|
|
446
|
+
// An ack resolves a request only when it arrives on THAT request's own
|
|
447
|
+
// ack event — a requestId echoed back on the wrong family is not an
|
|
448
|
+
// answer, however well-formed it looks.
|
|
449
|
+
const onAck = (ackEvent) => (ack) => {
|
|
387
450
|
if (!ack?.requestId)
|
|
388
451
|
return;
|
|
389
452
|
const waiter = this.pending.get(ack.requestId);
|
|
390
|
-
if (!waiter)
|
|
453
|
+
if (!waiter || waiter.ackEvent !== ackEvent)
|
|
391
454
|
return;
|
|
392
455
|
this.pending.delete(ack.requestId);
|
|
393
456
|
clearTimeout(waiter.timer);
|
|
394
457
|
waiter.resolve(ack);
|
|
395
458
|
};
|
|
396
|
-
this.socket.on(exports.DEDICATED_SERVICE_EVENTS.ack, onAck);
|
|
397
|
-
this.socket.on(exports.DEDICATED_SERVICE_EVENTS.templatesAck, onAck);
|
|
459
|
+
this.socket.on(exports.DEDICATED_SERVICE_EVENTS.ack, onAck(exports.DEDICATED_SERVICE_EVENTS.ack));
|
|
460
|
+
this.socket.on(exports.DEDICATED_SERVICE_EVENTS.templatesAck, onAck(exports.DEDICATED_SERVICE_EVENTS.templatesAck));
|
|
461
|
+
this.socket.on(exports.DEDICATED_TERMINAL_EVENTS.ack, onAck(exports.DEDICATED_TERMINAL_EVENTS.ack));
|
|
398
462
|
}
|
|
399
463
|
/** Resolves once the transport is up; rejects on connect failure. */
|
|
400
464
|
open() {
|
|
@@ -455,8 +519,21 @@ class DedicatedServiceChannel {
|
|
|
455
519
|
verb: `templates:${verb}`,
|
|
456
520
|
}, timeoutMs);
|
|
457
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* A persistent-terminal verb — `dedicated:terminal:list|kill|rename`,
|
|
524
|
+
* answered on `dedicated:terminal:ack`. Same requestId/ack discipline as
|
|
525
|
+
* the service verbs; the event family and the refusal's wording are the
|
|
526
|
+
* only differences.
|
|
527
|
+
*/
|
|
528
|
+
callTerminal(verb, payload, timeoutMs = exports.SERVICE_CALL_TIMEOUT_MS) {
|
|
529
|
+
return this.callEvent(`dedicated:terminal:${verb}`, exports.DEDICATED_TERMINAL_EVENTS.ack, payload, {
|
|
530
|
+
leaseId: typeof payload.leaseId === 'string' ? payload.leaseId : undefined,
|
|
531
|
+
verb: `terminal:${verb}`,
|
|
532
|
+
name: typeof payload.name === 'string' ? payload.name : undefined,
|
|
533
|
+
}, timeoutMs, describeTerminalRefusal);
|
|
534
|
+
}
|
|
458
535
|
/** The shared request/ack round-trip. */
|
|
459
|
-
callEvent(event, ackEvent, payload, context, timeoutMs) {
|
|
536
|
+
callEvent(event, ackEvent, payload, context, timeoutMs, describe = describeServiceRefusal) {
|
|
460
537
|
const requestId = `svc-${(0, node_crypto_1.randomUUID)()}`;
|
|
461
538
|
return new Promise((resolve, reject) => {
|
|
462
539
|
if (this.dead) {
|
|
@@ -469,9 +546,10 @@ class DedicatedServiceChannel {
|
|
|
469
546
|
}, timeoutMs);
|
|
470
547
|
this.pending.set(requestId, {
|
|
471
548
|
timer,
|
|
549
|
+
ackEvent,
|
|
472
550
|
resolve: (ack) => {
|
|
473
551
|
if (!ack.ok) {
|
|
474
|
-
reject(new DedicatedServiceError(
|
|
552
|
+
reject(new DedicatedServiceError(describe(ack, context), ack.code || 'SERVICE_REFUSED'));
|
|
475
553
|
return;
|
|
476
554
|
}
|
|
477
555
|
resolve({ result: ack.result, ack });
|
|
@@ -34,6 +34,8 @@ export interface TerminalStartAck {
|
|
|
34
34
|
ok?: boolean;
|
|
35
35
|
error?: string;
|
|
36
36
|
code?: string;
|
|
37
|
+
/** The tmux session a persistent start attached (`skrr-term-<name>`'s suffix). */
|
|
38
|
+
persistentName?: string;
|
|
37
39
|
}
|
|
38
40
|
/** Bytes of one output frame, without splitting a UTF-8 or ANSI sequence. */
|
|
39
41
|
export declare function decodeTerminalFrame(frame: TerminalOutputFrame): Buffer;
|
|
@@ -56,6 +58,25 @@ export interface DedicatedTerminalOptions {
|
|
|
56
58
|
daemonId: string;
|
|
57
59
|
cwd?: string;
|
|
58
60
|
initialCommand?: string;
|
|
61
|
+
/**
|
|
62
|
+
* A tmux-backed session (always-on §6.4): the machine keeps it alive across
|
|
63
|
+
* attaches, disconnects and daemon restarts, and a reconnect re-attaches it
|
|
64
|
+
* by name instead of opening a new shell. A daemon that predates
|
|
65
|
+
* `persistent_terminal_v1` silently ignores it — callers gate on the
|
|
66
|
+
* capability before asking.
|
|
67
|
+
*/
|
|
68
|
+
persistent?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The session suffix (`skrr-term-<name>`). The same name re-attaches the
|
|
71
|
+
* same session; omitted under `persistent`, the machine names the session
|
|
72
|
+
* `term-N` and the start ack reports it as `persistentName`.
|
|
73
|
+
*/
|
|
74
|
+
sessionName?: string;
|
|
75
|
+
/**
|
|
76
|
+
* The lease this terminal claims — the relay refuses a start that names a
|
|
77
|
+
* lease but resolves to another lease's daemon.
|
|
78
|
+
*/
|
|
79
|
+
leaseId?: string;
|
|
59
80
|
input: NodeJS.ReadStream;
|
|
60
81
|
output: NodeJS.WriteStream;
|
|
61
82
|
errorOutput: NodeJS.WritableStream;
|
|
@@ -78,13 +78,22 @@ function describeTerminalStartRefusal(ack) {
|
|
|
78
78
|
*/
|
|
79
79
|
function runDedicatedTerminal(options) {
|
|
80
80
|
const events = data_provider_1.HARNESS_TERMINAL_EVENTS;
|
|
81
|
-
const
|
|
81
|
+
const persistent = options.persistent === true;
|
|
82
82
|
const connect = options.connect ?? socket_io_client_1.io;
|
|
83
83
|
const { input, output, errorOutput } = options;
|
|
84
84
|
return new Promise((resolve, reject) => {
|
|
85
|
+
// The daemon keys a terminal by the sessionId in its start — one attach.
|
|
86
|
+
// A persistent session re-attaches across socket reconnects, and each
|
|
87
|
+
// attach is a fresh id over the same tmux name.
|
|
88
|
+
let sessionId = '';
|
|
85
89
|
let started = false;
|
|
86
90
|
let finished = false;
|
|
87
91
|
let rawMode = false;
|
|
92
|
+
// First attach armed the tty; a re-attach needs only the ack, not the
|
|
93
|
+
// setup again.
|
|
94
|
+
let sessionReady = false;
|
|
95
|
+
let reattaching = false;
|
|
96
|
+
let persistentName = options.sessionName;
|
|
88
97
|
const socket = connect((0, agentic_stream_1.toWebSocketOrigin)(options.baseURL), {
|
|
89
98
|
path: '/ws/agentic',
|
|
90
99
|
auth: {
|
|
@@ -94,10 +103,15 @@ function runDedicatedTerminal(options) {
|
|
|
94
103
|
clientOs: process.platform === 'darwin' ? 'macos' : process.platform,
|
|
95
104
|
},
|
|
96
105
|
transports: ['websocket'],
|
|
97
|
-
// A
|
|
106
|
+
// A bare PTY cannot be resumed: the relay forgets the session with the
|
|
98
107
|
// socket, and silently opening a NEW shell would drop the user into a
|
|
99
|
-
// different process tree mid-command
|
|
100
|
-
|
|
108
|
+
// different process tree mid-command — say it ended instead. A
|
|
109
|
+
// persistent session is the opposite by design: tmux keeps it, and the
|
|
110
|
+
// reconnect re-attaches it BY NAME, which is the same shell.
|
|
111
|
+
reconnection: persistent,
|
|
112
|
+
reconnectionAttempts: 10,
|
|
113
|
+
reconnectionDelay: 1_000,
|
|
114
|
+
reconnectionDelayMax: 5_000,
|
|
101
115
|
});
|
|
102
116
|
// Typed only once the shell's line editor says it is reading a command line
|
|
103
117
|
// (`createPendingShellCommand`, the rule the web terminal reads too). A
|
|
@@ -106,7 +120,10 @@ function runDedicatedTerminal(options) {
|
|
|
106
120
|
const pending = initialCommand
|
|
107
121
|
? (0, data_provider_1.createPendingShellCommand)({
|
|
108
122
|
command: initialCommand,
|
|
109
|
-
send: (data) =>
|
|
123
|
+
send: (data) => {
|
|
124
|
+
if (started)
|
|
125
|
+
socket.emit(events.input, { sessionId, data });
|
|
126
|
+
},
|
|
110
127
|
onStatusChange: (status) => {
|
|
111
128
|
const notice = describePendingCommand(status, initialCommand);
|
|
112
129
|
if (notice && !finished)
|
|
@@ -129,13 +146,20 @@ function runDedicatedTerminal(options) {
|
|
|
129
146
|
if (detachAt > 0) {
|
|
130
147
|
const typed = buffer.subarray(0, detachAt);
|
|
131
148
|
pending?.observeUserInput(typed);
|
|
132
|
-
|
|
149
|
+
if (started)
|
|
150
|
+
socket.emit(events.input, { sessionId, data: typed.toString('utf8') });
|
|
133
151
|
}
|
|
134
|
-
errorOutput.write(
|
|
152
|
+
errorOutput.write(persistent && persistentName
|
|
153
|
+
? `\r\n[detached — session '${persistentName}' keeps running on the machine]\r\n`
|
|
154
|
+
: '\r\n[closed the terminal]\r\n');
|
|
135
155
|
finish(0, true);
|
|
136
156
|
return;
|
|
137
157
|
}
|
|
138
158
|
pending?.observeUserInput(buffer);
|
|
159
|
+
// While a reconnect is in flight the attach is gone: the frame would be
|
|
160
|
+
// dropped daemon-side anyway, so it is not sent.
|
|
161
|
+
if (!started)
|
|
162
|
+
return;
|
|
139
163
|
socket.emit(events.input, { sessionId, data: buffer.toString('utf8') });
|
|
140
164
|
};
|
|
141
165
|
// A terminal that closes (SIGHUP) or a supervisor that stops us (SIGTERM):
|
|
@@ -145,9 +169,11 @@ function runDedicatedTerminal(options) {
|
|
|
145
169
|
// SIGINT's status, for both.
|
|
146
170
|
const onSigterm = () => finish(143, true);
|
|
147
171
|
const onSighup = () => finish(129, true);
|
|
148
|
-
const
|
|
172
|
+
const onStartTimeout = () => {
|
|
149
173
|
fail(new DedicatedTerminalError('The machine did not answer the terminal start in time. Its daemon may be busy or offline.', 'TERMINAL_START_TIMEOUT'));
|
|
150
|
-
}
|
|
174
|
+
};
|
|
175
|
+
let startTimer = setTimeout(onStartTimeout, exports.TERMINAL_START_TIMEOUT_MS);
|
|
176
|
+
startTimer.unref();
|
|
151
177
|
function restoreTty() {
|
|
152
178
|
input.off('data', onInput);
|
|
153
179
|
output.off('resize', onResize);
|
|
@@ -166,6 +192,7 @@ function runDedicatedTerminal(options) {
|
|
|
166
192
|
clearTimeout(startTimer);
|
|
167
193
|
pending?.close();
|
|
168
194
|
restoreTty();
|
|
195
|
+
detachReconnectFailed();
|
|
169
196
|
if (stopRemote && started)
|
|
170
197
|
socket.emit(events.stop, { sessionId });
|
|
171
198
|
socket.disconnect();
|
|
@@ -178,17 +205,34 @@ function runDedicatedTerminal(options) {
|
|
|
178
205
|
clearTimeout(startTimer);
|
|
179
206
|
pending?.close();
|
|
180
207
|
restoreTty();
|
|
208
|
+
detachReconnectFailed();
|
|
181
209
|
socket.disconnect();
|
|
182
210
|
reject(error);
|
|
183
211
|
}
|
|
184
212
|
socket.on('connect_error', (err) => {
|
|
213
|
+
// A failed retry while reattaching is not fatal — the attempt countdown
|
|
214
|
+
// keeps running and `reconnect_failed` ends it if they all miss.
|
|
215
|
+
if (persistent && reattaching)
|
|
216
|
+
return;
|
|
185
217
|
fail(new DedicatedTerminalError(`Could not connect to skrr: ${err.message}`, err.data?.code || 'TERMINAL_CONNECT_FAILED'));
|
|
186
218
|
});
|
|
187
219
|
socket.on('connect', () => {
|
|
220
|
+
if (finished)
|
|
221
|
+
return;
|
|
222
|
+
sessionId = (0, node_crypto_1.randomUUID)();
|
|
223
|
+
clearTimeout(startTimer);
|
|
224
|
+
startTimer = setTimeout(onStartTimeout, exports.TERMINAL_START_TIMEOUT_MS);
|
|
225
|
+
startTimer.unref();
|
|
188
226
|
socket.emit(events.start, {
|
|
189
227
|
runtimeId: options.runtimeId,
|
|
190
228
|
sessionId,
|
|
191
229
|
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
230
|
+
...(persistent ? { persistent: true } : {}),
|
|
231
|
+
// The RESOLVED name, not just the requested one: a `--new` session the
|
|
232
|
+
// machine named itself comes back in the ack, and a re-attach must
|
|
233
|
+
// name it — `tmux -A` without `-s` would open a fresh session instead.
|
|
234
|
+
...(persistentName ? { sessionName: persistentName } : {}),
|
|
235
|
+
...(options.leaseId ? { leaseId: options.leaseId } : {}),
|
|
192
236
|
...size(),
|
|
193
237
|
});
|
|
194
238
|
});
|
|
@@ -201,8 +245,20 @@ function runDedicatedTerminal(options) {
|
|
|
201
245
|
fail(new DedicatedTerminalError(describeTerminalStartRefusal(ack), ack.code || 'TERMINAL_START_REFUSED'));
|
|
202
246
|
return;
|
|
203
247
|
}
|
|
248
|
+
const reattached = reattaching;
|
|
204
249
|
started = true;
|
|
250
|
+
reattaching = false;
|
|
205
251
|
clearTimeout(startTimer);
|
|
252
|
+
if (typeof ack.persistentName === 'string' && ack.persistentName) {
|
|
253
|
+
persistentName = ack.persistentName;
|
|
254
|
+
}
|
|
255
|
+
if (sessionReady) {
|
|
256
|
+
if (reattached) {
|
|
257
|
+
errorOutput.write(`\r\n[reattached to session '${persistentName}']\r\n`);
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
sessionReady = true;
|
|
206
262
|
if (typeof input.setRawMode === 'function') {
|
|
207
263
|
input.setRawMode(true);
|
|
208
264
|
rawMode = true;
|
|
@@ -213,6 +269,9 @@ function runDedicatedTerminal(options) {
|
|
|
213
269
|
process.on('SIGTERM', onSigterm);
|
|
214
270
|
process.on('SIGHUP', onSighup);
|
|
215
271
|
pending?.beginSession();
|
|
272
|
+
if (persistent && persistentName) {
|
|
273
|
+
errorOutput.write(`\r\n[session '${persistentName}' is kept alive on the machine — Ctrl-] detaches, the same command re-attaches]\r\n`);
|
|
274
|
+
}
|
|
216
275
|
});
|
|
217
276
|
socket.on(events.output, (frame) => {
|
|
218
277
|
if (!frame || frame.sessionId !== sessionId || finished)
|
|
@@ -233,9 +292,45 @@ function runDedicatedTerminal(options) {
|
|
|
233
292
|
output.write(bytes);
|
|
234
293
|
pending?.observeOutput(bytes);
|
|
235
294
|
});
|
|
295
|
+
// socket.io reports a given-up reconnect on the manager; a bare EventEmitter
|
|
296
|
+
// test socket has none, so the socket itself carries the listener then.
|
|
297
|
+
const onReconnectFailed = () => {
|
|
298
|
+
if (finished || !persistent)
|
|
299
|
+
return;
|
|
300
|
+
const named = persistentName ? ` '${persistentName}'` : '';
|
|
301
|
+
errorOutput.write(`\r\n[could not reconnect to skrr — session${named} still runs on the machine; rerun the same command to reattach]\r\n`);
|
|
302
|
+
finish(1, false);
|
|
303
|
+
};
|
|
304
|
+
const manager = socket.io;
|
|
305
|
+
const onManager = typeof manager?.on === 'function';
|
|
306
|
+
if (onManager)
|
|
307
|
+
manager.on('reconnect_failed', onReconnectFailed);
|
|
308
|
+
else
|
|
309
|
+
socket.on('reconnect_failed', onReconnectFailed);
|
|
310
|
+
// The manager is SHARED per url+path (socket.io multiplexing), so the
|
|
311
|
+
// listener outlives this terminal unless it is removed on every exit.
|
|
312
|
+
function detachReconnectFailed() {
|
|
313
|
+
if (onManager && typeof manager?.off === 'function') {
|
|
314
|
+
manager.off('reconnect_failed', onReconnectFailed);
|
|
315
|
+
}
|
|
316
|
+
else if (!onManager) {
|
|
317
|
+
socket.off('reconnect_failed', onReconnectFailed);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
236
320
|
socket.on('disconnect', () => {
|
|
237
321
|
if (finished)
|
|
238
322
|
return;
|
|
323
|
+
if (persistent) {
|
|
324
|
+
// The tmux session outlives the link — the tmux client's death only
|
|
325
|
+
// detaches. socket.io retries and the next start re-attaches by name.
|
|
326
|
+
started = false;
|
|
327
|
+
if (!reattaching) {
|
|
328
|
+
reattaching = true;
|
|
329
|
+
const named = persistentName ? ` '${persistentName}'` : '';
|
|
330
|
+
errorOutput.write(`\r\n[lost the connection to skrr — reattaching to session${named}…]\r\n`);
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
239
334
|
if (!started) {
|
|
240
335
|
fail(new DedicatedTerminalError('The connection to skrr closed before the terminal started.', 'TERMINAL_DISCONNECTED'));
|
|
241
336
|
return;
|
|
@@ -223,6 +223,16 @@ export interface ManagedEngineHooks {
|
|
|
223
223
|
* never from the env this hook returned.
|
|
224
224
|
*/
|
|
225
225
|
credentialMode: () => 'managed' | 'byok';
|
|
226
|
+
/**
|
|
227
|
+
* `prepare` fell back to BYOK AND no provider credential will reach the
|
|
228
|
+
* engine — the exact case where the fallback line already warned that the
|
|
229
|
+
* first request will fail. The caller uses this AFTER a non-zero engine
|
|
230
|
+
* exit to tie an opaque engine failure (an `Unexpected server error` that
|
|
231
|
+
* says nothing about auth) back to the missing credential, without
|
|
232
|
+
* refusing the launch up front: the engine can carry a stored credential
|
|
233
|
+
* `hasProviderCredential` cannot see (OSK-11949).
|
|
234
|
+
*/
|
|
235
|
+
unprovisionedByok: () => boolean;
|
|
226
236
|
}
|
|
227
237
|
export interface ManagedEngineHooksInput {
|
|
228
238
|
subcommand: string | null;
|
|
@@ -999,8 +999,15 @@ function createManagedEngineHooks(input) {
|
|
|
999
999
|
* is not reported to the user as a decision about their account (OSK-3961).
|
|
1000
1000
|
*/
|
|
1001
1001
|
let tearingDown = false;
|
|
1002
|
+
/**
|
|
1003
|
+
* `prepare` answered `{}` with no credential the engine could inherit —
|
|
1004
|
+
* latched, so a post-exit caller can reconnect an opaque engine failure to
|
|
1005
|
+
* the auth cause the fallback line already named (OSK-11949).
|
|
1006
|
+
*/
|
|
1007
|
+
let unprovisioned = false;
|
|
1002
1008
|
return {
|
|
1003
1009
|
session: () => handle,
|
|
1010
|
+
unprovisionedByok: () => unprovisioned,
|
|
1004
1011
|
// A live handle means a broker is running and the engine's base URLs point at
|
|
1005
1012
|
// it, which is exactly when re-admitting an ambient provider key would let the
|
|
1006
1013
|
// engine bypass the relay (or 401 preferring a stale `x-api-key`). No handle
|
|
@@ -1012,6 +1019,7 @@ function createManagedEngineHooks(input) {
|
|
|
1012
1019
|
if (skip) {
|
|
1013
1020
|
if (skip.announce)
|
|
1014
1021
|
log(`Managed inference not used: ${skip.detail}. ${fallbackTail(env)}`);
|
|
1022
|
+
unprovisioned = !(0, first_party_harness_1.hasProviderCredential)(env);
|
|
1015
1023
|
return {};
|
|
1016
1024
|
}
|
|
1017
1025
|
try {
|
|
@@ -1063,6 +1071,7 @@ function createManagedEngineHooks(input) {
|
|
|
1063
1071
|
if (failure.action === 'refuse') {
|
|
1064
1072
|
throw new first_party_harness_1.EngineStartRefusedError(failure.code, failure.line);
|
|
1065
1073
|
}
|
|
1074
|
+
unprovisioned = !(0, first_party_harness_1.hasProviderCredential)(env);
|
|
1066
1075
|
return {};
|
|
1067
1076
|
}
|
|
1068
1077
|
},
|
|
@@ -42,6 +42,7 @@ const first_party_harness_1 = require("./first-party-harness");
|
|
|
42
42
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
43
43
|
const harness_tiers_1 = require("./harness-tiers");
|
|
44
44
|
const first_party_harness_managed_1 = require("./first-party-harness-managed");
|
|
45
|
+
const first_party_harness_project_trust_1 = require("./first-party-harness-project-trust");
|
|
45
46
|
/**
|
|
46
47
|
* Project-level state dir — the ENGINE's own project marker
|
|
47
48
|
* (`FIRST_PARTY_HARNESS_ENGINE.projectDirectory`). Basename deliberately differs
|
|
@@ -490,6 +491,42 @@ function permissionAuthorityCheck(env) {
|
|
|
490
491
|
detail: 'daemon-managed sessions adjudicate every tool call; no trust tier auto-approves',
|
|
491
492
|
};
|
|
492
493
|
}
|
|
494
|
+
/**
|
|
495
|
+
* OSK-11958 — whether `skrr code` will load THIS directory's project configuration.
|
|
496
|
+
*
|
|
497
|
+
* Reported because the answer changes what the engine can do before any tool
|
|
498
|
+
* call exists: a project's plugins and custom tools run on load, and its
|
|
499
|
+
* permission block can loosen what the agent does without asking. Never a
|
|
500
|
+
* `warn`: an untrusted project with config is the safe default working as
|
|
501
|
+
* designed, and one with none has nothing to decide.
|
|
502
|
+
*/
|
|
503
|
+
function projectConfigCheck(cwd, env) {
|
|
504
|
+
const root = (0, first_party_harness_project_trust_1.findProjectRoot)(cwd);
|
|
505
|
+
const config = (0, first_party_harness_project_trust_1.findProjectConfig)(cwd, root, env);
|
|
506
|
+
if (config.length === 0) {
|
|
507
|
+
return {
|
|
508
|
+
name: 'project-config',
|
|
509
|
+
status: 'ok',
|
|
510
|
+
detail: 'this project ships no agent configuration — nothing to trust',
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const listed = config.map((file) => node_path_1.default.relative(root, file) || '.').join(', ');
|
|
514
|
+
if ((0, first_party_harness_project_trust_1.isProjectTrusted)(root, env)) {
|
|
515
|
+
return {
|
|
516
|
+
name: 'project-config',
|
|
517
|
+
status: 'ok',
|
|
518
|
+
detail: `trusted — ${listed} load for \`skrr code\` and \`skrr code run\``,
|
|
519
|
+
remedy: `Recorded in ${(0, first_party_harness_project_trust_1.trustRecordPath)(env)}; delete this project's entry there to stop trusting it.`,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
name: 'project-config',
|
|
524
|
+
status: 'ok',
|
|
525
|
+
detail: `not trusted — ${listed} will NOT load (instructions files still do)`,
|
|
526
|
+
remedy: `Project configuration can run code on this machine and loosen permissions. Pass ` +
|
|
527
|
+
`${first_party_harness_project_trust_1.TRUST_PROJECT_FLAG} once, or answer "always" when the TUI asks, to load it.`,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
493
530
|
/**
|
|
494
531
|
* Hosts the engine's WL-2.7 egress policy denies, and the one it still permits.
|
|
495
532
|
*
|
|
@@ -759,7 +796,9 @@ function sessionAuthorityCheck(env) {
|
|
|
759
796
|
name: 'session-authority',
|
|
760
797
|
status: 'ok',
|
|
761
798
|
detail: 'standalone — the engine decides. No daemon permission adjudication, no run fence, no ' +
|
|
762
|
-
'scoped platform credential, no durable task context, no worktree isolation. ' +
|
|
799
|
+
'scoped platform credential, no durable task context, no worktree isolation. The TUI ' +
|
|
800
|
+
'asks before edits, commands and web access unless OPENCODE_PERMISSION or ' + // brand-leak-ok:compat upstream env var name
|
|
801
|
+
'--engine-permissions says otherwise; `skrr code run` keeps the engine config. ' +
|
|
763
802
|
'Daemon-supervised sessions enter through `skrr code acp`, not through this command.',
|
|
764
803
|
remedy: 'Intended for local use, and nothing to fix. Worth knowing because it is NOT the same ' +
|
|
765
804
|
'trust plane as a daemon-dispatched run — and neither plane is an OS-level sandbox ' +
|
|
@@ -828,6 +867,7 @@ function runDoctor(cwd = process.cwd(), env = process.env) {
|
|
|
828
867
|
stateDirCheck(cwd, env),
|
|
829
868
|
compatCheck(cwd, env),
|
|
830
869
|
opencodeCheck(cwd),
|
|
870
|
+
projectConfigCheck(cwd, env),
|
|
831
871
|
// Before the trust/permission/credential trio, because it frames all three:
|
|
832
872
|
// what a tier grants and who enforces it both depend on which plane you are on.
|
|
833
873
|
sessionAuthorityCheck(env),
|
|
@@ -122,9 +122,10 @@ export declare const MANAGED_SESSION_ENV = "OVERSKY_MANAGED_SESSION";
|
|
|
122
122
|
* by `skrr` itself. Keying an AUTHORITY answer on it therefore tells a user with
|
|
123
123
|
* no daemon at all that "a daemon adjudicates every tool call", which is the
|
|
124
124
|
* exact conflation `sessionAuthorityCheck`'s own docstring was written to warn
|
|
125
|
-
* about. It is not a subtle misread:
|
|
126
|
-
*
|
|
127
|
-
*
|
|
125
|
+
* about. It is not a subtle misread: nothing on this plane routes a tool call
|
|
126
|
+
* to the daemon. Since OSK-11958 the bare TUI asks the PERSON at the terminal
|
|
127
|
+
* before edits and commands (`INTERACTIVE_PERMISSION_DEFAULT`), and `skrr code
|
|
128
|
+
* run` keeps the engine's own config — neither is daemon adjudication.
|
|
128
129
|
*
|
|
129
130
|
* Ordered deliberately: DAEMON IDENTITY FIRST. Only the daemon can set
|
|
130
131
|
* `OVERSKY_AUTONOMOUS_SESSION` or the runtime context a daemon session carries,
|