@skrr-ai/cli 0.1.49 → 0.1.51
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 +75 -6
- package/dist/commands/agents/chat.d.ts +14 -0
- package/dist/commands/agents/chat.js +37 -1
- package/dist/commands/agents/create.js +14 -7
- package/dist/commands/agents/show.d.ts +9 -0
- package/dist/commands/agents/show.js +53 -0
- package/dist/commands/agents/update.d.ts +9 -0
- package/dist/commands/agents/update.js +38 -5
- package/dist/commands/balance/show.d.ts +34 -0
- package/dist/commands/balance/show.js +92 -2
- 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/doctor.d.ts +1 -0
- package/dist/commands/commitments/doctor.js +19 -4
- package/dist/commands/commitments/effective-policy.js +40 -1
- package/dist/commands/commitments/explain.d.ts +17 -0
- package/dist/commands/commitments/explain.js +40 -0
- package/dist/commands/commitments/next-tick.js +3 -1
- package/dist/commands/commitments/preflight.js +10 -0
- package/dist/commands/commitments/show.js +11 -1
- package/dist/commands/daemon/config.d.ts +15 -0
- package/dist/commands/daemon/config.js +38 -0
- package/dist/commands/daemon/index.js +1 -0
- package/dist/commands/daemon/status.js +15 -1
- package/dist/commands/goals/create.d.ts +1 -0
- package/dist/commands/goals/create.js +22 -0
- package/dist/commands/harnesses/models.d.ts +54 -0
- package/dist/commands/harnesses/models.js +100 -7
- package/dist/commands/harnesses/show.d.ts +11 -0
- package/dist/commands/harnesses/show.js +35 -0
- package/dist/commands/harnesses/update.d.ts +28 -1
- package/dist/commands/harnesses/update.js +73 -10
- 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/outbox/list.d.ts +12 -1
- package/dist/commands/outbox/list.js +31 -2
- package/dist/commands/revoke-daemon.js +9 -0
- package/dist/commands/spaces/create.d.ts +1 -0
- package/dist/commands/spaces/create.js +17 -0
- package/dist/commands/spaces/show.js +5 -0
- package/dist/commands/spaces/update.d.ts +1 -0
- package/dist/commands/spaces/update.js +16 -0
- package/dist/commands/tasks/create.d.ts +1 -0
- package/dist/commands/tasks/create.js +6 -0
- package/dist/commands/tasks/list.js +1 -0
- package/dist/commands/tasks/show.js +45 -0
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +22 -0
- package/dist/commands/tasks/upsert.d.ts +1 -0
- package/dist/commands/tasks/upsert.js +16 -0
- package/dist/lib/agent-config.d.ts +2 -0
- package/dist/lib/agent-config.js +3 -1
- package/dist/lib/agentic-stream.d.ts +68 -1
- package/dist/lib/agentic-stream.js +294 -7
- package/dist/lib/api-fetch.js +19 -0
- package/dist/lib/auth-core-init.d.ts +2 -10
- package/dist/lib/auth-core-init.js +20 -67
- package/dist/lib/auth-failure.d.ts +33 -0
- package/dist/lib/auth-failure.js +65 -0
- package/dist/lib/auth-storage.d.ts +14 -0
- package/dist/lib/auth-storage.js +14 -0
- package/dist/lib/commitments.d.ts +37 -2
- package/dist/lib/commitments.js +148 -0
- package/dist/lib/daemonBroker.d.ts +134 -31
- package/dist/lib/daemonBroker.js +376 -31
- 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/delegated-cli.js +4 -1
- package/dist/lib/exec-runtime-binary.d.ts +3 -1
- package/dist/lib/exec-runtime-binary.js +4 -2
- package/dist/lib/first-party-harness-broker.d.ts +10 -0
- package/dist/lib/first-party-harness-broker.js +13 -1
- 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/harnesses.d.ts +10 -0
- package/dist/lib/hosted-machines.d.ts +10 -1
- package/dist/lib/hosted-machines.js +36 -2
- package/dist/lib/live-runtime-binary.d.ts +40 -0
- package/dist/lib/live-runtime-binary.js +193 -0
- package/dist/lib/login.d.ts +23 -0
- package/dist/lib/login.js +60 -10
- package/dist/lib/machine-spend-cap.d.ts +23 -0
- package/dist/lib/machine-spend-cap.js +68 -0
- package/dist/lib/message-intent.js +3 -1
- package/dist/lib/node-adapter.js +15 -3
- package/dist/lib/tasks.d.ts +20 -0
- package/dist/lib/tasks.js +54 -0
- 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/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
- 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 +133 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +189 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
- 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.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +16 -10
- 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/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/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
- 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/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
- 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 +133 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +182 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
- 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.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +16 -10
- 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/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/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
- 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 +61 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3418 -3344
- package/oclif.manifest.json +24813 -24242
- package/package.json +4 -1
|
@@ -576,16 +576,17 @@ function strippedProviderCredentialNames(env = process.env) {
|
|
|
576
576
|
* under that name, rather than rely on inheritance.
|
|
577
577
|
*
|
|
578
578
|
* The layering, in full: sanitized parent → the user's own model credential (BYOK
|
|
579
|
-
* only) → {@link engineOwnedEnv} → `extraEnv`.
|
|
579
|
+
* only) → {@link engineOwnedEnv} → the project policy (OSK-11958) → `extraEnv`.
|
|
580
580
|
*/
|
|
581
581
|
function engineSpawnEnv(env, extraEnv,
|
|
582
582
|
// Required, not defaulted: the safe value differs per call and a parameter that
|
|
583
583
|
// can be forgotten on a security boundary will be.
|
|
584
|
-
mode) {
|
|
584
|
+
mode, policyEnv = {}) {
|
|
585
585
|
const spawnEnv = {
|
|
586
586
|
...(0, auth_core_1.sanitizeSpawnEnv)(env),
|
|
587
587
|
...(mode === 'managed' ? {} : inheritedProviderCredentials(env)),
|
|
588
588
|
...engineOwnedEnv(env),
|
|
589
|
+
...policyEnv,
|
|
589
590
|
...extraEnv,
|
|
590
591
|
};
|
|
591
592
|
if (mode !== 'managed')
|
|
@@ -671,7 +672,7 @@ async function execEngine(args, env = process.env, options = {}) {
|
|
|
671
672
|
const spawnArgs = options.finalizeArgs ? options.finalizeArgs([...args]) : args;
|
|
672
673
|
// Read AFTER `prepare`, because that is when the hook knows what it acquired.
|
|
673
674
|
const mode = options.credentialMode?.() ?? 'byok';
|
|
674
|
-
return await spawnEngine(bin, spawnArgs, env, extraEnv, mode, outcome);
|
|
675
|
+
return await spawnEngine(bin, spawnArgs, env, extraEnv, mode, outcome, options.policyEnv);
|
|
675
676
|
}
|
|
676
677
|
finally {
|
|
677
678
|
// In a `finally` rather than on `exit`, so a spawn error and a Ctrl-C both
|
|
@@ -683,12 +684,12 @@ async function execEngine(args, env = process.env, options = {}) {
|
|
|
683
684
|
function spawnEngine(bin, args, env, extraEnv, mode,
|
|
684
685
|
// Written into rather than returned, so the `finally` above can read it on
|
|
685
686
|
// every path — including one where this promise rejects.
|
|
686
|
-
outcome) {
|
|
687
|
+
outcome, policyEnv) {
|
|
687
688
|
return new Promise((resolve, reject) => {
|
|
688
689
|
const isWinShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(bin);
|
|
689
690
|
const child = (0, node_child_process_1.spawn)(bin, args, {
|
|
690
691
|
stdio: 'inherit',
|
|
691
|
-
env: engineSpawnEnv(env, extraEnv, mode),
|
|
692
|
+
env: engineSpawnEnv(env, extraEnv, mode, policyEnv),
|
|
692
693
|
...(isWinShim ? { shell: true } : {}),
|
|
693
694
|
});
|
|
694
695
|
// `stdio: 'inherit'` puts the child in this process group, so Ctrl-C reaches
|
package/dist/lib/harnesses.d.ts
CHANGED
|
@@ -64,6 +64,16 @@ export interface Harness {
|
|
|
64
64
|
version?: number;
|
|
65
65
|
claudeEngine?: 'cli' | 'sdk';
|
|
66
66
|
lastSeenAt?: string;
|
|
67
|
+
/** This harness's own stored permission-mode fallback, when set. */
|
|
68
|
+
defaultPermissionMode?: string | null;
|
|
69
|
+
/**
|
|
70
|
+
* The permission modes this harness can honour, and where the answer came
|
|
71
|
+
* from — sent by `GET /api/harnesses/:id` for a local row (OSK-12130).
|
|
72
|
+
*/
|
|
73
|
+
permissionModes?: {
|
|
74
|
+
modes: string[];
|
|
75
|
+
source: 'daemon' | 'daemon_record' | 'static' | 'vocabulary' | string;
|
|
76
|
+
};
|
|
67
77
|
/**
|
|
68
78
|
* Registration metadata. A managed machine's rows carry `machineProduct` and
|
|
69
79
|
* `machineLeaseId`, which is the only link from a lease to the harness rows its
|
|
@@ -124,13 +124,22 @@ export declare function hostedMachineStatus(session: HostedMachineSession | unde
|
|
|
124
124
|
export declare function hostedMachineDisplayName(session: HostedMachineSession | undefined | null): string;
|
|
125
125
|
export declare function hostedMachineRepoSummary(session: HostedMachineSession | undefined | null): string;
|
|
126
126
|
export declare function hostedMachineErrorSummary(session: HostedMachineSession | undefined | null): string;
|
|
127
|
+
/**
|
|
128
|
+
* Whether tenant code on this machine can read the daemon's credentials
|
|
129
|
+
* (OSK-11981). The server always sends a mode — a machine with no record is
|
|
130
|
+
* reported as `shared_uid` — so a missing field here means an older server,
|
|
131
|
+
* and is printed as unknown rather than as the reassuring answer.
|
|
132
|
+
*/
|
|
133
|
+
export declare function hostedMachineIsolationSummary(session: HostedMachineSession | undefined | null): string;
|
|
127
134
|
/** Stable, user-facing snapshot for long provisioning waits. */
|
|
128
135
|
export declare function hostedMachineProgressSummary(session: HostedMachineSession | undefined | null): string;
|
|
129
136
|
export declare function isHostedMachineReady(session: HostedMachineSession | undefined | null): boolean;
|
|
130
137
|
export declare function isHostedMachineTerminal(session: HostedMachineSession | undefined | null): boolean;
|
|
131
138
|
export declare function parseDurationMs(value: string | undefined, flagName: string): number | undefined;
|
|
132
139
|
export declare function sleep(ms: number): Promise<void>;
|
|
133
|
-
export declare function formatHostedMachineApiError(err: unknown
|
|
140
|
+
export declare function formatHostedMachineApiError(err: unknown, context?: {
|
|
141
|
+
bin?: string;
|
|
142
|
+
}): string | null;
|
|
134
143
|
export declare function startHostedMachine(input: StartHostedMachineInput): Promise<HostedMachineResponse>;
|
|
135
144
|
export declare function listHostedMachines(scope?: HostedMachineScope & {
|
|
136
145
|
limit?: number;
|
|
@@ -8,6 +8,7 @@ exports.hostedMachineStatus = hostedMachineStatus;
|
|
|
8
8
|
exports.hostedMachineDisplayName = hostedMachineDisplayName;
|
|
9
9
|
exports.hostedMachineRepoSummary = hostedMachineRepoSummary;
|
|
10
10
|
exports.hostedMachineErrorSummary = hostedMachineErrorSummary;
|
|
11
|
+
exports.hostedMachineIsolationSummary = hostedMachineIsolationSummary;
|
|
11
12
|
exports.hostedMachineProgressSummary = hostedMachineProgressSummary;
|
|
12
13
|
exports.isHostedMachineReady = isHostedMachineReady;
|
|
13
14
|
exports.isHostedMachineTerminal = isHostedMachineTerminal;
|
|
@@ -26,6 +27,7 @@ exports.waitForHostedMachineReady = waitForHostedMachineReady;
|
|
|
26
27
|
exports.offlineForLabel = offlineForLabel;
|
|
27
28
|
const api_fetch_1 = require("./api-fetch");
|
|
28
29
|
const edge_error_page_1 = require("./edge-error-page");
|
|
30
|
+
const machine_spend_cap_1 = require("./machine-spend-cap");
|
|
29
31
|
function curateHostedMachineList(response) {
|
|
30
32
|
return {
|
|
31
33
|
object: 'list',
|
|
@@ -107,6 +109,21 @@ function hostedMachineErrorSummary(session) {
|
|
|
107
109
|
return error;
|
|
108
110
|
return error.message || error.class || '';
|
|
109
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Whether tenant code on this machine can read the daemon's credentials
|
|
114
|
+
* (OSK-11981). The server always sends a mode — a machine with no record is
|
|
115
|
+
* reported as `shared_uid` — so a missing field here means an older server,
|
|
116
|
+
* and is printed as unknown rather than as the reassuring answer.
|
|
117
|
+
*/
|
|
118
|
+
function hostedMachineIsolationSummary(session) {
|
|
119
|
+
const isolation = session?.identityIsolation;
|
|
120
|
+
if (!isolation?.mode)
|
|
121
|
+
return 'unknown (server did not report it)';
|
|
122
|
+
if (isolation.mode === 'daemon_workload_split') {
|
|
123
|
+
return `daemon credentials isolated from tenant code (contract ${isolation.contract || '?'})`;
|
|
124
|
+
}
|
|
125
|
+
return `shared uid — tenant code can read the daemon's credentials (${isolation.reason || 'unspecified'})`;
|
|
126
|
+
}
|
|
110
127
|
/** Stable, user-facing snapshot for long provisioning waits. */
|
|
111
128
|
function hostedMachineProgressSummary(session) {
|
|
112
129
|
const status = hostedMachineStatus(session);
|
|
@@ -142,7 +159,7 @@ function parseDurationMs(value, flagName) {
|
|
|
142
159
|
function sleep(ms) {
|
|
143
160
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
144
161
|
}
|
|
145
|
-
function formatHostedMachineApiError(err) {
|
|
162
|
+
function formatHostedMachineApiError(err, context = {}) {
|
|
146
163
|
if (!(err instanceof api_fetch_1.ApiFetchError) || !err.body) {
|
|
147
164
|
return null;
|
|
148
165
|
}
|
|
@@ -155,13 +172,30 @@ function formatHostedMachineApiError(err) {
|
|
|
155
172
|
if (edge) {
|
|
156
173
|
return edge.message;
|
|
157
174
|
}
|
|
175
|
+
let message;
|
|
158
176
|
try {
|
|
159
177
|
const parsed = JSON.parse(err.body);
|
|
160
|
-
|
|
178
|
+
message = parsed.message || parsed.error || parsed.code || null;
|
|
161
179
|
}
|
|
162
180
|
catch {
|
|
163
181
|
return err.body.trim() || null;
|
|
164
182
|
}
|
|
183
|
+
// A monthly-cap refusal carries its figures in `details`; print them, and say
|
|
184
|
+
// this is not the plan or the credits (OSK-11946). The dedicated CLI has done
|
|
185
|
+
// the figures since OSK-9552; the hosted one dropped them.
|
|
186
|
+
if (message && (0, machine_spend_cap_1.isMonthlyCapRefusal)(err.body)) {
|
|
187
|
+
const figures = (0, machine_spend_cap_1.describeMonthlyCapFigures)(err.body);
|
|
188
|
+
return [
|
|
189
|
+
message,
|
|
190
|
+
figures ? `${figures}.` : '',
|
|
191
|
+
machine_spend_cap_1.MONTHLY_CAP_IS_SEPARATE,
|
|
192
|
+
"Start with a higher --spending-limit-cents, or raise an existing machine's cap with " +
|
|
193
|
+
`\`${context.bin || 'skrr'} harnesses leases billing <lease-id> --spending-limit-cents <n>\`.`,
|
|
194
|
+
]
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
.join(' ');
|
|
197
|
+
}
|
|
198
|
+
return message;
|
|
165
199
|
}
|
|
166
200
|
async function startHostedMachine(input) {
|
|
167
201
|
return (0, api_fetch_1.apiFetch)('/api/agentic/sandbox-sessions', {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface LiveProgram {
|
|
2
|
+
/** The executable the OS reports for the live pid. */
|
|
3
|
+
exec: string;
|
|
4
|
+
/** For an interpreter-launched daemon, the script it runs. */
|
|
5
|
+
script: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface LiveRuntimeDeps {
|
|
8
|
+
/** pid of the live daemon for `profile`, or null. */
|
|
9
|
+
livePid(profile: string): number | null;
|
|
10
|
+
/** The live pid's program, or null when the OS will not say. */
|
|
11
|
+
programOf(pid: number): LiveProgram | null;
|
|
12
|
+
realpath(p: string): string;
|
|
13
|
+
isExecutable(p: string): boolean;
|
|
14
|
+
}
|
|
15
|
+
export type StatusBinaryDecision = {
|
|
16
|
+
kind: 'same';
|
|
17
|
+
binary: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'unknown';
|
|
20
|
+
binary: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'forward-live';
|
|
23
|
+
binary: string;
|
|
24
|
+
pathBinary: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'mismatch';
|
|
27
|
+
binary: string;
|
|
28
|
+
live: string;
|
|
29
|
+
};
|
|
30
|
+
/** The profile a forwarded argv names, `default` otherwise. */
|
|
31
|
+
export declare function profileFromArgs(args: readonly string[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Decide which binary renders `status`. Never throws: a probe that cannot
|
|
34
|
+
* answer leaves the PATH binary in place (`unknown`).
|
|
35
|
+
*/
|
|
36
|
+
export declare function decideStatusBinary(pathBinary: string, profile: string, deps: LiveRuntimeDeps): StatusBinaryDecision;
|
|
37
|
+
/** One stderr line naming the binary that rendered status, when it is not obvious. */
|
|
38
|
+
export declare function describeStatusBinary(decision: StatusBinaryDecision): string | null;
|
|
39
|
+
export declare function scriptFromArgs(exec: string, args: string): string | null;
|
|
40
|
+
export declare function defaultLiveRuntimeDeps(): LiveRuntimeDeps;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.profileFromArgs = profileFromArgs;
|
|
7
|
+
exports.decideStatusBinary = decideStatusBinary;
|
|
8
|
+
exports.describeStatusBinary = describeStatusBinary;
|
|
9
|
+
exports.scriptFromArgs = scriptFromArgs;
|
|
10
|
+
exports.defaultLiveRuntimeDeps = defaultLiveRuntimeDeps;
|
|
11
|
+
/**
|
|
12
|
+
* Which runtime binary is the LIVE daemon running, and is it the one
|
|
13
|
+
* `skrr daemon status` is about to forward to? (OSK-12102)
|
|
14
|
+
*
|
|
15
|
+
* `execRuntimeBinary` forwards to the first `skrrd` on PATH. That is right for
|
|
16
|
+
* lifecycle verbs, where PATH order is the person's choice of install. It is
|
|
17
|
+
* wrong for `status`, whose whole subject is the daemon that is running: with
|
|
18
|
+
* an old dev build early on PATH and the release daemon live, the header named
|
|
19
|
+
* `.../releases/0.8.62/skrrd` while the body was rendered by the dev build —
|
|
20
|
+
* its old layout, no per-category permission table — and nothing on screen
|
|
21
|
+
* said two binaries were involved.
|
|
22
|
+
*
|
|
23
|
+
* The live daemon's pid comes from its island bootstrap file (the same
|
|
24
|
+
* discovery the CLI's login broker uses); the program from the OS. When the
|
|
25
|
+
* live program is itself a runtime executable, status is forwarded to it.
|
|
26
|
+
* When it cannot be exec'd directly (a `node .../dist/index.js` dev daemon),
|
|
27
|
+
* status stays on the PATH binary and says so.
|
|
28
|
+
*/
|
|
29
|
+
const node_child_process_1 = require("node:child_process");
|
|
30
|
+
const node_fs_1 = require("node:fs");
|
|
31
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
32
|
+
const daemonBroker_1 = require("./daemonBroker");
|
|
33
|
+
const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
34
|
+
function safeRealpath(deps, p) {
|
|
35
|
+
try {
|
|
36
|
+
return deps.realpath(p);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return p;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function isRuntimeName(p) {
|
|
43
|
+
const base = node_path_1.default
|
|
44
|
+
.basename(p)
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/\.(exe|cmd)$/, '');
|
|
47
|
+
return exec_runtime_binary_1.RUNTIME_BINARY_NAMES.some((name) => base === name || base.startsWith(`${name}-`));
|
|
48
|
+
}
|
|
49
|
+
/** The profile a forwarded argv names, `default` otherwise. */
|
|
50
|
+
function profileFromArgs(args) {
|
|
51
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
52
|
+
const arg = args[i];
|
|
53
|
+
if (arg === '--')
|
|
54
|
+
break;
|
|
55
|
+
if (arg === '--profile' && args[i + 1] && !args[i + 1].startsWith('-'))
|
|
56
|
+
return args[i + 1];
|
|
57
|
+
if (arg.startsWith('--profile=') && arg.length > '--profile='.length) {
|
|
58
|
+
return arg.slice('--profile='.length);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return 'default';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Decide which binary renders `status`. Never throws: a probe that cannot
|
|
65
|
+
* answer leaves the PATH binary in place (`unknown`).
|
|
66
|
+
*/
|
|
67
|
+
function decideStatusBinary(pathBinary, profile, deps) {
|
|
68
|
+
let program = null;
|
|
69
|
+
try {
|
|
70
|
+
const pid = deps.livePid(profile);
|
|
71
|
+
program = pid ? deps.programOf(pid) : null;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
program = null;
|
|
75
|
+
}
|
|
76
|
+
if (!program)
|
|
77
|
+
return { kind: 'unknown', binary: pathBinary };
|
|
78
|
+
const pathReal = safeRealpath(deps, pathBinary);
|
|
79
|
+
const execReal = safeRealpath(deps, program.exec);
|
|
80
|
+
const scriptReal = program.script ? safeRealpath(deps, program.script) : null;
|
|
81
|
+
if (pathReal === execReal || (scriptReal !== null && pathReal === scriptReal)) {
|
|
82
|
+
return { kind: 'same', binary: pathBinary };
|
|
83
|
+
}
|
|
84
|
+
// Only a runtime executable is forwarded to. A `node`/Electron-hosted daemon
|
|
85
|
+
// has an interpreter as its exec, and running the interpreter with status
|
|
86
|
+
// argv would be nonsense.
|
|
87
|
+
const directlyRunnable = program.script === null &&
|
|
88
|
+
(isRuntimeName(program.exec) || isRuntimeName(execReal)) &&
|
|
89
|
+
deps.isExecutable(program.exec);
|
|
90
|
+
if (directlyRunnable) {
|
|
91
|
+
return { kind: 'forward-live', binary: program.exec, pathBinary };
|
|
92
|
+
}
|
|
93
|
+
return { kind: 'mismatch', binary: pathBinary, live: program.script ?? program.exec };
|
|
94
|
+
}
|
|
95
|
+
/** One stderr line naming the binary that rendered status, when it is not obvious. */
|
|
96
|
+
function describeStatusBinary(decision) {
|
|
97
|
+
switch (decision.kind) {
|
|
98
|
+
case 'forward-live':
|
|
99
|
+
return (`Rendered by the live daemon's binary ${decision.binary} ` +
|
|
100
|
+
`(the first skrrd on PATH is ${decision.pathBinary}, a different build).`);
|
|
101
|
+
case 'mismatch':
|
|
102
|
+
return (`Rendered by ${decision.binary}, the first skrrd on PATH — NOT the live daemon's ` +
|
|
103
|
+
`program (${decision.live}). This layout and these checks are that build's, not the live one's.`);
|
|
104
|
+
default:
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function darwinProgramOf(pid) {
|
|
109
|
+
const out = (args) => (0, node_child_process_1.execFileSync)('ps', ['-p', String(pid), '-o', ...args], {
|
|
110
|
+
encoding: 'utf8',
|
|
111
|
+
timeout: 2_000,
|
|
112
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
113
|
+
}).trim();
|
|
114
|
+
const exec = out(['comm=']);
|
|
115
|
+
if (!exec || !node_path_1.default.isAbsolute(exec))
|
|
116
|
+
return null;
|
|
117
|
+
return { exec, script: scriptFromArgs(exec, out(['args='])) };
|
|
118
|
+
}
|
|
119
|
+
function linuxProgramOf(pid) {
|
|
120
|
+
const exec = (0, node_fs_1.readlinkSync)(`/proc/${pid}/exe`);
|
|
121
|
+
let script = null;
|
|
122
|
+
try {
|
|
123
|
+
const cmdline = (0, node_fs_1.readFileSync)(`/proc/${pid}/cmdline`, 'utf8');
|
|
124
|
+
script = scriptFromArgs(exec, cmdline.split('\0').filter(Boolean).join(' '));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
script = null;
|
|
128
|
+
}
|
|
129
|
+
return { exec, script };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* For an interpreter-hosted daemon (`node --import x.mjs /x/dist/index.js
|
|
133
|
+
* start …`), the script: the first argument after the exec that is neither an
|
|
134
|
+
* option nor the value of a module-loading option. `ps -o args=` is
|
|
135
|
+
* space-joined, so a path containing a space is not recovered — the result is
|
|
136
|
+
* only ever used for display and an equality check, where a miss degrades to
|
|
137
|
+
* the "mismatch" note rather than to a wrong forward.
|
|
138
|
+
*/
|
|
139
|
+
const INTERPRETER_VALUE_OPTIONS = new Set([
|
|
140
|
+
'-r',
|
|
141
|
+
'--require',
|
|
142
|
+
'--import',
|
|
143
|
+
'--loader',
|
|
144
|
+
'--experimental-loader',
|
|
145
|
+
'-e',
|
|
146
|
+
'--eval',
|
|
147
|
+
]);
|
|
148
|
+
function scriptFromArgs(exec, args) {
|
|
149
|
+
if (isRuntimeName(exec))
|
|
150
|
+
return null;
|
|
151
|
+
const rest = args.startsWith(exec) ? args.slice(exec.length) : args.replace(/^\S+/, '');
|
|
152
|
+
const tokens = rest.split(/\s+/).filter(Boolean);
|
|
153
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
154
|
+
const token = tokens[i];
|
|
155
|
+
if (INTERPRETER_VALUE_OPTIONS.has(token)) {
|
|
156
|
+
i += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (token.startsWith('-'))
|
|
160
|
+
continue;
|
|
161
|
+
return token;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
function defaultLiveRuntimeDeps() {
|
|
166
|
+
return {
|
|
167
|
+
livePid(profile) {
|
|
168
|
+
for (const candidate of (0, daemonBroker_1.resolveExactProfileBootstrapPaths)(profile)) {
|
|
169
|
+
const bootstrap = (0, daemonBroker_1.readBootstrap)(candidate);
|
|
170
|
+
if (typeof bootstrap?.pid === 'number' && bootstrap.pid > 0)
|
|
171
|
+
return bootstrap.pid;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
},
|
|
175
|
+
programOf(pid) {
|
|
176
|
+
if (process.platform === 'darwin')
|
|
177
|
+
return darwinProgramOf(pid);
|
|
178
|
+
if (process.platform === 'linux')
|
|
179
|
+
return linuxProgramOf(pid);
|
|
180
|
+
return null;
|
|
181
|
+
},
|
|
182
|
+
realpath: (p) => (0, node_fs_1.realpathSync)(p),
|
|
183
|
+
isExecutable(p) {
|
|
184
|
+
try {
|
|
185
|
+
(0, node_fs_1.accessSync)(p, node_fs_1.constants.X_OK);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
package/dist/lib/login.d.ts
CHANGED
|
@@ -38,6 +38,29 @@ export interface CliLoginWithRefreshTokenOptions {
|
|
|
38
38
|
*/
|
|
39
39
|
cliId: string;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* What a shell with no terminal can do instead (OSK-12117). The refusal used to
|
|
43
|
+
* name only CI tokens, which is not a sign-in: an agent session asking for one
|
|
44
|
+
* could only hand the job to a person typing `! skrr login`.
|
|
45
|
+
*/
|
|
46
|
+
export declare const HEADLESS_LOGIN_HINT: string;
|
|
47
|
+
/**
|
|
48
|
+
* The line printed when the daemon descriptor this CLI found is for another
|
|
49
|
+
* server (OSK-12117).
|
|
50
|
+
*
|
|
51
|
+
* It used to assert "the skrr background service on this computer is logged in
|
|
52
|
+
* to <url>" and recommend repointing the CLI there. What the CLI actually knows
|
|
53
|
+
* is narrower: ONE descriptor file it read names that server. That file can
|
|
54
|
+
* belong to another profile's daemon (observed: a localhost:3080 descriptor
|
|
55
|
+
* while this machine's default daemon was connected to skrr.ai), and in that
|
|
56
|
+
* case both the claim and the advice were wrong — the advice by moving a
|
|
57
|
+
* production CLI onto a development server. So say what was read, from where,
|
|
58
|
+
* and how to check the daemon itself.
|
|
59
|
+
*/
|
|
60
|
+
export declare function describeBrokerBaseUrlMismatch(failure: {
|
|
61
|
+
daemonServerUrl?: string;
|
|
62
|
+
descriptorPath?: string;
|
|
63
|
+
}, serverUrl: string): string;
|
|
41
64
|
/**
|
|
42
65
|
* Top-level dispatcher. Picks PKCE localhost vs device-code per the
|
|
43
66
|
* decision tree in the roadmap and persists the result.
|
package/dist/lib/login.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HEADLESS_LOGIN_HINT = void 0;
|
|
4
|
+
exports.describeBrokerBaseUrlMismatch = describeBrokerBaseUrlMismatch;
|
|
3
5
|
exports.cliLogin = cliLogin;
|
|
4
6
|
exports.cliLoginWithRefreshToken = cliLoginWithRefreshToken;
|
|
5
7
|
/**
|
|
@@ -35,6 +37,33 @@ const device_code_1 = require("./device-code");
|
|
|
35
37
|
const device_identity_1 = require("./device-identity");
|
|
36
38
|
const oauthLogin_1 = require("./oauthLogin");
|
|
37
39
|
const daemonBroker_1 = require("./daemonBroker");
|
|
40
|
+
/**
|
|
41
|
+
* What a shell with no terminal can do instead (OSK-12117). The refusal used to
|
|
42
|
+
* name only CI tokens, which is not a sign-in: an agent session asking for one
|
|
43
|
+
* could only hand the job to a person typing `! skrr login`.
|
|
44
|
+
*/
|
|
45
|
+
exports.HEADLESS_LOGIN_HINT = '`skrr login --device-code` works without a terminal: it prints a URL and a code for a ' +
|
|
46
|
+
'person to approve in any browser, and finishes when they do. For CI, use ' +
|
|
47
|
+
'`skrr create-token` and set OVERSKY_TOKEN=osk_ci_...';
|
|
48
|
+
/**
|
|
49
|
+
* The line printed when the daemon descriptor this CLI found is for another
|
|
50
|
+
* server (OSK-12117).
|
|
51
|
+
*
|
|
52
|
+
* It used to assert "the skrr background service on this computer is logged in
|
|
53
|
+
* to <url>" and recommend repointing the CLI there. What the CLI actually knows
|
|
54
|
+
* is narrower: ONE descriptor file it read names that server. That file can
|
|
55
|
+
* belong to another profile's daemon (observed: a localhost:3080 descriptor
|
|
56
|
+
* while this machine's default daemon was connected to skrr.ai), and in that
|
|
57
|
+
* case both the claim and the advice were wrong — the advice by moving a
|
|
58
|
+
* production CLI onto a development server. So say what was read, from where,
|
|
59
|
+
* and how to check the daemon itself.
|
|
60
|
+
*/
|
|
61
|
+
function describeBrokerBaseUrlMismatch(failure, serverUrl) {
|
|
62
|
+
const where = failure.descriptorPath ? ` (${failure.descriptorPath})` : '';
|
|
63
|
+
return (` Skipped the daemon hand-off: the daemon descriptor found${where} is for ` +
|
|
64
|
+
`${failure.daemonServerUrl}, not ${serverUrl}. It may belong to a different profile's ` +
|
|
65
|
+
'daemon; `skrr daemon status` shows which server this profile’s daemon is on.');
|
|
66
|
+
}
|
|
38
67
|
/**
|
|
39
68
|
* Top-level dispatcher. Picks PKCE localhost vs device-code per the
|
|
40
69
|
* decision tree in the roadmap and persists the result.
|
|
@@ -83,6 +112,22 @@ async function cliLogin(opts = {}) {
|
|
|
83
112
|
baseURL: serverUrl,
|
|
84
113
|
});
|
|
85
114
|
if (brokerResult.ok) {
|
|
115
|
+
if (brokerResult.brokered) {
|
|
116
|
+
// Broker-mode hand-off: the daemon served a short-lived access token
|
|
117
|
+
// from a family IT owns. Nothing credential-shaped is persisted here
|
|
118
|
+
// — the token lives for this process and the next `skrr` re-redeems
|
|
119
|
+
// over loopback. An explicit PKCE/device-code login further down
|
|
120
|
+
// still writes a real user-owned family, unchanged.
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(" This machine is signed in through the platform's managed hand-off.");
|
|
123
|
+
return {
|
|
124
|
+
token: brokerResult.accessToken,
|
|
125
|
+
...(brokerResult.accessExpiresAt !== undefined
|
|
126
|
+
? { expiresAt: brokerResult.accessExpiresAt }
|
|
127
|
+
: {}),
|
|
128
|
+
flow: 'daemon-broker',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
86
131
|
console.log('');
|
|
87
132
|
console.log(' Authorized via the skrr background service on this computer (no browser needed).');
|
|
88
133
|
return {
|
|
@@ -132,25 +177,30 @@ async function cliLogin(opts = {}) {
|
|
|
132
177
|
}
|
|
133
178
|
if (brokerResult.reason === 'base_url_mismatch' && brokerResult.daemonServerUrl) {
|
|
134
179
|
console.log('');
|
|
135
|
-
console.log(
|
|
136
|
-
console.log(` Skipping the daemon-broker handoff. Run \`skrr login --base-url ${brokerResult.daemonServerUrl}\` to use the daemon instead.`);
|
|
180
|
+
console.log(describeBrokerBaseUrlMismatch(brokerResult, serverUrl));
|
|
137
181
|
}
|
|
138
182
|
}
|
|
139
|
-
if ((0, ssh_detect_1.isNonInteractive)()) {
|
|
140
|
-
throw new Error(brokerRateLimited
|
|
141
|
-
? 'The skrr background service on this computer is being rate-limited while brokering logins, and this ' +
|
|
142
|
-
'shell cannot run the browser flow instead. The limit is per 15-minute window — ' +
|
|
143
|
-
'retry shortly. For CI, use `skrr create-token` and set OVERSKY_TOKEN=osk_ci_...'
|
|
144
|
-
: 'skrr login requires an interactive terminal or an SSH session. ' +
|
|
145
|
-
'For CI, use `skrr create-token` and set OVERSKY_TOKEN=osk_ci_...');
|
|
146
|
-
}
|
|
147
183
|
// --- Decision tree (first match wins) ---
|
|
184
|
+
//
|
|
185
|
+
// Device code BEFORE the terminal check (OSK-12117). It needs no terminal of
|
|
186
|
+
// its own: it prints a URL and a code, a person approves them in any browser,
|
|
187
|
+
// and this process polls until they do. That is the one way an agent session
|
|
188
|
+
// or any other shell without a TTY can finish a login, and the refusal below
|
|
189
|
+
// used to fire first — so `--device-code` was refused for lacking the very
|
|
190
|
+
// thing it does not use.
|
|
148
191
|
if (opts.forceDeviceCode) {
|
|
149
192
|
return runDeviceCode(serverUrl, cliId, 'user_requested', ssh);
|
|
150
193
|
}
|
|
151
194
|
if (envForce) {
|
|
152
195
|
return runDeviceCode(serverUrl, cliId, 'env_force', ssh);
|
|
153
196
|
}
|
|
197
|
+
if ((0, ssh_detect_1.isNonInteractive)()) {
|
|
198
|
+
throw new Error(brokerRateLimited
|
|
199
|
+
? 'The skrr background service on this computer is being rate-limited while brokering logins, and this ' +
|
|
200
|
+
'shell cannot run the browser flow instead. The limit is per 15-minute window — ' +
|
|
201
|
+
`retry shortly, or sign in now with ${exports.HEADLESS_LOGIN_HINT}`
|
|
202
|
+
: `skrr login opens a browser, and this shell has no terminal to run it from. ${exports.HEADLESS_LOGIN_HINT}`);
|
|
203
|
+
}
|
|
154
204
|
if (ssh.detected) {
|
|
155
205
|
// Device-code's "open this URL on your laptop" UX is the correct
|
|
156
206
|
// move under SSH — the localhost callback would bind on the remote
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The figures behind a machine monthly-cap refusal, for every machine product.
|
|
3
|
+
*
|
|
4
|
+
* `RUNTIME_SPENDING_LIMIT_EXCEEDED` carries the cap, this month's spend and
|
|
5
|
+
* where the refused action would take it, as integer cents in `details`. The
|
|
6
|
+
* dedicated CLI learned to print them in OSK-9552; the hosted CLI kept printing
|
|
7
|
+
* the bare sentence, "Projected Hosted Machine usage would exceed the monthly
|
|
8
|
+
* cap." A person who had just watched `skrr balance show` report a large
|
|
9
|
+
* balance with overage on then read that as "you have money but we won't take
|
|
10
|
+
* it" (OSK-11946). The machine cap is a SEPARATE ceiling from the plan
|
|
11
|
+
* allowance and the credits, and overage does not raise it; without figures and
|
|
12
|
+
* that sentence nothing on the screen says so.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatCapCents(cents: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* `Cap $500.00; spent this month $12.22; this would bring it to $512.22`, or ''
|
|
17
|
+
* when the body is not a cap refusal or carries no figures.
|
|
18
|
+
*/
|
|
19
|
+
export declare function describeMonthlyCapFigures(body: string | undefined): string;
|
|
20
|
+
/** Whether `body` is a machine monthly-cap refusal at all. */
|
|
21
|
+
export declare function isMonthlyCapRefusal(body: string | undefined): boolean;
|
|
22
|
+
/** The one sentence that says which ceiling this is — and which it is not. */
|
|
23
|
+
export declare const MONTHLY_CAP_IS_SEPARATE = "This is the machine monthly cap, separate from your plan allowance and credits; overage does not raise it.";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The figures behind a machine monthly-cap refusal, for every machine product.
|
|
4
|
+
*
|
|
5
|
+
* `RUNTIME_SPENDING_LIMIT_EXCEEDED` carries the cap, this month's spend and
|
|
6
|
+
* where the refused action would take it, as integer cents in `details`. The
|
|
7
|
+
* dedicated CLI learned to print them in OSK-9552; the hosted CLI kept printing
|
|
8
|
+
* the bare sentence, "Projected Hosted Machine usage would exceed the monthly
|
|
9
|
+
* cap." A person who had just watched `skrr balance show` report a large
|
|
10
|
+
* balance with overage on then read that as "you have money but we won't take
|
|
11
|
+
* it" (OSK-11946). The machine cap is a SEPARATE ceiling from the plan
|
|
12
|
+
* allowance and the credits, and overage does not raise it; without figures and
|
|
13
|
+
* that sentence nothing on the screen says so.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.MONTHLY_CAP_IS_SEPARATE = void 0;
|
|
17
|
+
exports.formatCapCents = formatCapCents;
|
|
18
|
+
exports.describeMonthlyCapFigures = describeMonthlyCapFigures;
|
|
19
|
+
exports.isMonthlyCapRefusal = isMonthlyCapRefusal;
|
|
20
|
+
function formatCapCents(cents) {
|
|
21
|
+
const sign = cents < 0 ? '-' : '';
|
|
22
|
+
return `${sign}$${(Math.abs(cents) / 100).toFixed(2)}`;
|
|
23
|
+
}
|
|
24
|
+
/** The refusal's `code` and `details`, when the body is JSON; null otherwise. */
|
|
25
|
+
function parseRefusal(body) {
|
|
26
|
+
if (!body)
|
|
27
|
+
return null;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(body);
|
|
30
|
+
const details = parsed?.details && typeof parsed.details === 'object' && !Array.isArray(parsed.details)
|
|
31
|
+
? parsed.details
|
|
32
|
+
: undefined;
|
|
33
|
+
return { code: typeof parsed?.code === 'string' ? parsed.code : undefined, details };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `Cap $500.00; spent this month $12.22; this would bring it to $512.22`, or ''
|
|
41
|
+
* when the body is not a cap refusal or carries no figures.
|
|
42
|
+
*/
|
|
43
|
+
function describeMonthlyCapFigures(body) {
|
|
44
|
+
const refusal = parseRefusal(body);
|
|
45
|
+
if (refusal?.code !== 'RUNTIME_SPENDING_LIMIT_EXCEEDED' || !refusal.details)
|
|
46
|
+
return '';
|
|
47
|
+
const details = refusal.details;
|
|
48
|
+
const cents = (key) => {
|
|
49
|
+
const value = details[key];
|
|
50
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
51
|
+
};
|
|
52
|
+
const limit = cents('limitCents');
|
|
53
|
+
const spent = cents('currentSpendCents');
|
|
54
|
+
const projected = cents('projectedCents');
|
|
55
|
+
return [
|
|
56
|
+
limit !== null ? `Cap ${formatCapCents(limit)}` : '',
|
|
57
|
+
spent !== null ? `spent this month ${formatCapCents(spent)}` : '',
|
|
58
|
+
projected !== null ? `this would bring it to ${formatCapCents(projected)}` : '',
|
|
59
|
+
]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join('; ');
|
|
62
|
+
}
|
|
63
|
+
/** Whether `body` is a machine monthly-cap refusal at all. */
|
|
64
|
+
function isMonthlyCapRefusal(body) {
|
|
65
|
+
return parseRefusal(body)?.code === 'RUNTIME_SPENDING_LIMIT_EXCEEDED';
|
|
66
|
+
}
|
|
67
|
+
/** The one sentence that says which ceiling this is — and which it is not. */
|
|
68
|
+
exports.MONTHLY_CAP_IS_SEPARATE = 'This is the machine monthly cap, separate from your plan allowance and credits; overage does not raise it.';
|
|
@@ -11,6 +11,7 @@ exports.canonicalSlackChannelRequest = canonicalSlackChannelRequest;
|
|
|
11
11
|
exports.canonicalSlackOwnerDmRequest = canonicalSlackOwnerDmRequest;
|
|
12
12
|
exports.rejectAutonomousSlackTarget = rejectAutonomousSlackTarget;
|
|
13
13
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
14
|
+
const loopback_http_1 = require("@skrr-ai/auth-core/loopback-http");
|
|
14
15
|
const api_fetch_1 = require("./api-fetch");
|
|
15
16
|
const daemonBroker_1 = require("./daemonBroker");
|
|
16
17
|
const runtime_context_1 = require("./runtime-context");
|
|
@@ -152,7 +153,8 @@ async function submitDaemonLoopbackMessageIntent({ context, request, env, bootst
|
|
|
152
153
|
});
|
|
153
154
|
try {
|
|
154
155
|
const response = await Promise.race([
|
|
155
|
-
|
|
156
|
+
// The daemon secret and session token stay on loopback (OSK-12134).
|
|
157
|
+
(fetchImpl || loopback_http_1.loopbackFetch)(`http://${bootstrap.host}:${bootstrap.port}/v1/agent/message-intents`, {
|
|
156
158
|
method: 'POST',
|
|
157
159
|
headers: {
|
|
158
160
|
Authorization: `Bearer ${bootstrap.secret}`,
|