@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.
Files changed (117) hide show
  1. package/README.md +5 -4
  2. package/dist/base-command.js +16 -4
  3. package/dist/commands/agent-worker.d.ts +12 -0
  4. package/dist/commands/agent-worker.js +140 -0
  5. package/dist/commands/agents/create.js +14 -7
  6. package/dist/commands/agents/update.js +11 -5
  7. package/dist/commands/code/index.d.ts +1 -0
  8. package/dist/commands/code/index.js +49 -5
  9. package/dist/commands/code/run.js +5 -1
  10. package/dist/commands/commitments/cycles.d.ts +19 -0
  11. package/dist/commands/commitments/cycles.js +46 -0
  12. package/dist/commands/commitments/effective-policy.js +26 -1
  13. package/dist/commands/commitments/explain.d.ts +17 -0
  14. package/dist/commands/commitments/explain.js +40 -0
  15. package/dist/commands/goals/create.d.ts +1 -0
  16. package/dist/commands/goals/create.js +22 -0
  17. package/dist/commands/logout.js +12 -1
  18. package/dist/commands/machines/dedicated/index.js +1 -1
  19. package/dist/commands/machines/dedicated/sign-in.js +4 -1
  20. package/dist/commands/machines/dedicated/terminal/kill.d.ts +21 -0
  21. package/dist/commands/machines/dedicated/terminal/kill.js +55 -0
  22. package/dist/commands/machines/dedicated/terminal/ls.d.ts +22 -0
  23. package/dist/commands/machines/dedicated/terminal/ls.js +70 -0
  24. package/dist/commands/machines/dedicated/terminal/rename.d.ts +23 -0
  25. package/dist/commands/machines/dedicated/terminal/rename.js +64 -0
  26. package/dist/commands/machines/dedicated/terminal.d.ts +9 -0
  27. package/dist/commands/machines/dedicated/terminal.js +27 -2
  28. package/dist/commands/machines/hosted/connect.js +2 -2
  29. package/dist/commands/machines/hosted/destroy.js +1 -1
  30. package/dist/commands/machines/hosted/exec.js +1 -1
  31. package/dist/commands/machines/hosted/list.js +1 -1
  32. package/dist/commands/machines/hosted/pause.js +1 -1
  33. package/dist/commands/machines/hosted/pull.js +1 -1
  34. package/dist/commands/machines/hosted/resume.js +1 -1
  35. package/dist/commands/machines/hosted/start.js +2 -2
  36. package/dist/commands/machines/hosted/status.js +2 -1
  37. package/dist/commands/tasks/create.js +1 -0
  38. package/dist/commands/tasks/list.js +1 -0
  39. package/dist/commands/tasks/update.js +1 -0
  40. package/dist/lib/agent-config.d.ts +2 -0
  41. package/dist/lib/agent-config.js +3 -1
  42. package/dist/lib/api-fetch.js +19 -0
  43. package/dist/lib/auth-storage.d.ts +14 -0
  44. package/dist/lib/auth-storage.js +14 -0
  45. package/dist/lib/commitments.d.ts +17 -2
  46. package/dist/lib/commitments.js +114 -0
  47. package/dist/lib/daemon-installer.d.ts +6 -5
  48. package/dist/lib/daemon-installer.js +4 -17
  49. package/dist/lib/daemonBroker.d.ts +120 -31
  50. package/dist/lib/daemonBroker.js +313 -20
  51. package/dist/lib/dedicated-lease-command.d.ts +14 -0
  52. package/dist/lib/dedicated-lease-command.js +30 -1
  53. package/dist/lib/dedicated-machines.js +8 -25
  54. package/dist/lib/dedicated-service-command.d.ts +11 -3
  55. package/dist/lib/dedicated-service-command.js +20 -3
  56. package/dist/lib/dedicated-service.d.ts +46 -0
  57. package/dist/lib/dedicated-service.js +85 -7
  58. package/dist/lib/dedicated-terminal.d.ts +21 -0
  59. package/dist/lib/dedicated-terminal.js +104 -9
  60. package/dist/lib/first-party-harness-broker.d.ts +10 -0
  61. package/dist/lib/first-party-harness-broker.js +9 -0
  62. package/dist/lib/first-party-harness-doctor.js +41 -1
  63. package/dist/lib/first-party-harness-managed.d.ts +4 -3
  64. package/dist/lib/first-party-harness-project-trust.d.ts +125 -0
  65. package/dist/lib/first-party-harness-project-trust.js +364 -0
  66. package/dist/lib/first-party-harness.d.ts +9 -2
  67. package/dist/lib/first-party-harness.js +6 -5
  68. package/dist/lib/hosted-machines.d.ts +10 -1
  69. package/dist/lib/hosted-machines.js +36 -2
  70. package/dist/lib/login.js +16 -0
  71. package/dist/lib/machine-spend-cap.d.ts +23 -0
  72. package/dist/lib/machine-spend-cap.js +68 -0
  73. package/dist/lib/node-adapter.js +15 -3
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +90 -0
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +113 -0
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +47 -0
  77. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +69 -0
  78. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +109 -0
  79. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +171 -0
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +1 -1
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +1 -1
  82. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -2
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +8 -3
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +9 -4
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -9
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.d.ts +6 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.js +32 -9
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.d.ts +38 -0
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.js +66 -1
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.d.ts +81 -0
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.js +87 -0
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +6 -1
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +90 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +105 -0
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +47 -0
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +66 -0
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +109 -0
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +164 -0
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +1 -1
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +1 -1
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +8 -3
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +9 -4
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -9
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.d.ts +6 -0
  108. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.js +31 -9
  109. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.d.ts +38 -0
  110. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.js +64 -0
  111. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.d.ts +81 -0
  112. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.js +80 -0
  113. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +6 -1
  114. package/dist/node_modules/@skrr-ai/auth-core/package.json +41 -1
  115. package/dist/node_modules/@skrr-ai/data-provider/index.js +3413 -3343
  116. package/oclif.manifest.json +8930 -8466
  117. package/package.json +4 -1
package/README.md CHANGED
@@ -1247,11 +1247,12 @@ Create a new goal. `--title` is required unless supplied via `--from-json`.
1247
1247
  ```bash
1248
1248
  sky goals create --title "Ship Q2 roadmap"
1249
1249
  sky goals create --title "Milestone 1" --level milestone --parent <parent-id>
1250
+ sky goals create --title "Launch" --label <existing-label-id>
1250
1251
  sky goals create --from-json goal.json
1251
1252
  ```
1252
1253
 
1253
1254
  Flags: `--title`, `--description`, `--level`, `--parent`, `--owner-agent`,
1254
- `--dri-agent`, `--dri-user`, `--from-json <file|->`, `--json`.
1255
+ `--dri-agent`, `--dri-user`, `--label` (repeatable), `--from-json <file|->`, `--json`.
1255
1256
 
1256
1257
  ### `sky goals update <id>`
1257
1258
 
@@ -1351,7 +1352,7 @@ sky tasks create --title "QA pass" \
1351
1352
  --assignee-agent <agent-id> \
1352
1353
  --assignee-agent <other-agent-id> \
1353
1354
  --tag release --tag urgent \
1354
- --label-id <existing-label-id>
1355
+ --label <existing-label-id>
1355
1356
  sky tasks create --title "Follow up" --source-conversation <conversation-id>
1356
1357
  sky tasks create --title "Spec task" --external-key ws0/spec --dry-run --json
1357
1358
  sky tasks create --from-json task.json
@@ -1379,14 +1380,14 @@ are globally resolvable.
1379
1380
 
1380
1381
  Tags are lightweight free-form keywords. Labels are shared workspace taxonomy
1381
1382
  and are attached by opaque ID; discover existing IDs with
1382
- `sky tasks labels list --json`. Both are optional and repeatable at creation.
1383
+ `sky labels list --applicable-to task`. Both are optional and repeatable at creation.
1383
1384
 
1384
1385
  Flags: `--title`, `--description`, `--description-format`, `--external-key`,
1385
1386
  `--default-agent`,
1386
1387
  `--priority`, `--parent`, `--space`, `--goal`,
1387
1388
  `--source-conversation`, `--source-message`, `--assignee-agent`
1388
1389
  (repeatable), `--assignee-user` (repeatable), `--due-date`, `--tag`
1389
- (repeatable), `--label-id` (repeatable), `--from-json <file|->`, `--dry-run`,
1390
+ (repeatable), `--label` (repeatable; `--label-id` is a compatibility alias), `--from-json <file|->`, `--dry-run`,
1390
1391
  `--json`.
1391
1392
 
1392
1393
  ### `sky tasks upsert`
@@ -298,10 +298,22 @@ class BaseCommand extends core_1.Command {
298
298
  const accessToken = autoResult.outcome.accessToken;
299
299
  this.resolvedCredential = {
300
300
  token: accessToken,
301
- // The backend the broker actually wrote to. Hard-coding
302
- // 'keychain' labelled a Linux credential (always the file)
303
- // "macOS Keychain" in the same process that minted it.
304
- source: autoResult.outcome.storedIn === 'keychain' ? 'keychain' : 'file',
301
+ // A `brokered` outcome is a daemon-owned access token held for
302
+ // THIS process only — nothing was persisted, so no stored-backend
303
+ // label applies. 'env-token' is the honest slot the shared
304
+ // CredentialSource union has for "a token this process holds in
305
+ // memory": `ensureFreshCliCredential` skips non-stored sources,
306
+ // which is correct — renewal happens by re-redeeming the
307
+ // descriptor (see api-fetch's 401 path), not by rotating a
308
+ // family that was never ours.
309
+ source: autoResult.outcome.brokered
310
+ ? 'env-token'
311
+ : // The backend the broker actually wrote to. Hard-coding
312
+ // 'keychain' labelled a Linux credential (always the file)
313
+ // "macOS Keychain" in the same process that minted it.
314
+ autoResult.outcome.storedIn === 'keychain'
315
+ ? 'keychain'
316
+ : 'file',
305
317
  kind: (0, auth_core_1.classifyTokenKind)(accessToken),
306
318
  };
307
319
  }
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class AgentWorker extends Command {
3
+ static summary: string;
4
+ static description: string;
5
+ static flags: {
6
+ socket: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
7
+ 'heartbeat-ms': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<void>;
10
+ /** `revoked`/`invalid_handle` — the binding is gone, not the transport. */
11
+ private bindingRefused;
12
+ }
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const guest_service_1 = require("../lib/guest-service");
5
+ /**
6
+ * `skrr agent-worker` — the long-lived guest agent worker (§10.2).
7
+ *
8
+ * The blessed `agent` service template runs this command inside a Dedicated
9
+ * Runtime guest. The control plane mints the binding at service-create admit
10
+ * and injects it into the spec's env:
11
+ *
12
+ * SKRR_AGENT_ID the agent this worker holds presence for
13
+ * SKRR_AGENT_HANDLE the presence capability — `awh_<id>.aws_<secret>`
14
+ *
15
+ * On start the worker registers with the daemon's broker socket, then
16
+ * heartbeats until it is stopped (SIGTERM → bye) or the binding is revoked —
17
+ * `service remove` deletes the spec, the broker's next answer is `revoked`,
18
+ * and the worker exits. The service lifecycle IS the handle lifecycle.
19
+ *
20
+ * The handle is never logged and never echoed — the daemon's reply names the
21
+ * agent and service it validated, which is everything worth printing.
22
+ */
23
+ const DEFAULT_SOCKET = '/run/skrr-dedicated-runtime/agent-worker/agent-worker.sock';
24
+ const DEFAULT_HEARTBEAT_MS = 30_000;
25
+ /** Register retries cover a service started while the daemon is restarting. */
26
+ const REGISTER_RETRY_MS = 3_000;
27
+ const REGISTER_BUDGET_MS = 120_000;
28
+ /** Consecutive transport failures before the worker gives up and lets the reconciler see the crash. */
29
+ const HEARTBEAT_FAILURE_LIMIT = 10;
30
+ const BYE_TIMEOUT_MS = 5_000;
31
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
32
+ class AgentWorker extends core_1.Command {
33
+ static summary = 'Run the agent-worker presence loop for this machine (launched by the agent service template)';
34
+ static description = 'Reads the server-issued binding from SKRR_AGENT_ID / SKRR_AGENT_HANDLE, registers with ' +
35
+ 'the machine’s daemon over the agent-worker socket, and holds presence with heartbeats ' +
36
+ 'until the service is stopped or the binding is revoked. Not meant to be run by hand — ' +
37
+ 'declare it with `skrr machines dedicated service create --template agent --param agent=<agent-id>` ' +
38
+ 'or the service API.';
39
+ static flags = {
40
+ socket: core_1.Flags.string({
41
+ description: 'Agent-worker socket path (defaults to the machine’s broker socket)',
42
+ env: 'SKRR_AGENT_WORKER_SOCKET',
43
+ }),
44
+ 'heartbeat-ms': core_1.Flags.integer({
45
+ description: 'Heartbeat interval override in milliseconds (the broker’s answer wins)',
46
+ min: 1000,
47
+ }),
48
+ };
49
+ async run() {
50
+ const { flags } = await this.parse(AgentWorker);
51
+ const agentId = (process.env.SKRR_AGENT_ID ?? '').trim();
52
+ const handle = (process.env.SKRR_AGENT_HANDLE ?? '').trim();
53
+ if (!agentId || !handle) {
54
+ this.error('SKRR_AGENT_ID and SKRR_AGENT_HANDLE must both be set — the control plane injects them ' +
55
+ 'when a service is created with an agent binding. This process is not meant to be run ' +
56
+ 'by hand; create the service with `--template agent --param agent=<agent-id>` instead.', { exit: 2 });
57
+ }
58
+ const socketPath = flags.socket ?? DEFAULT_SOCKET;
59
+ const heartbeatOverride = flags['heartbeat-ms'];
60
+ const call = (verb, timeoutMs) => (0, guest_service_1.callGuestServiceSocket)({ verb, handle }, { socketPath, ...(timeoutMs ? { timeoutMs } : {}) });
61
+ // ---- register --------------------------------------------------------
62
+ // The service can start while the daemon is restarting; transport errors
63
+ // retry inside a budget, a refused binding never does.
64
+ const deadline = Date.now() + REGISTER_BUDGET_MS;
65
+ let heartbeatMs = heartbeatOverride ?? DEFAULT_HEARTBEAT_MS;
66
+ for (;;) {
67
+ try {
68
+ const reply = await call('register');
69
+ if (typeof reply.agentId === 'string' && reply.agentId !== agentId) {
70
+ this.error(`The daemon validated this handle for ${reply.agentId}, not ${agentId} — ` +
71
+ 'the service spec is inconsistent. Remove and recreate the service.', { exit: 2 });
72
+ }
73
+ if (typeof reply.heartbeatMs === 'number' && reply.heartbeatMs >= 1000) {
74
+ heartbeatMs = reply.heartbeatMs;
75
+ }
76
+ this.log(`agent-worker registered: agent=${reply.agentId ?? agentId} ` +
77
+ `service=${reply.serviceName ?? '?'} heartbeat=${heartbeatMs}ms`);
78
+ break;
79
+ }
80
+ catch (error) {
81
+ if (this.bindingRefused(error)) {
82
+ this.error(`The daemon refused this worker's binding: ${error.message}. ` +
83
+ 'The service spec predates the handle — remove and recreate the service.', { exit: 1 });
84
+ }
85
+ if (Date.now() >= deadline) {
86
+ this.error(`The agent-worker socket never answered within ${REGISTER_BUDGET_MS / 1000}s ` +
87
+ `(${error.message}) — the machine's daemon is not offering the ` +
88
+ 'agent-worker lane.', { exit: 1 });
89
+ }
90
+ await sleep(REGISTER_RETRY_MS);
91
+ }
92
+ }
93
+ // ---- heartbeat -------------------------------------------------------
94
+ let failures = 0;
95
+ let stopping = false;
96
+ const stop = () => {
97
+ stopping = true;
98
+ };
99
+ process.on('SIGTERM', stop);
100
+ process.on('SIGINT', stop);
101
+ while (!stopping) {
102
+ await sleep(heartbeatMs);
103
+ if (stopping)
104
+ break;
105
+ try {
106
+ await call('heartbeat');
107
+ failures = 0;
108
+ }
109
+ catch (error) {
110
+ if (this.bindingRefused(error)) {
111
+ // The binding is dead — almost always `service remove` having
112
+ // deleted the spec. Exit cleanly: there is nothing to retry, and
113
+ // under an on-failure restart policy a clean exit keeps the
114
+ // reconciler from flapping a corpse.
115
+ this.log('The agent binding was revoked — exiting.');
116
+ process.exit(0);
117
+ }
118
+ failures += 1;
119
+ if (failures >= HEARTBEAT_FAILURE_LIMIT) {
120
+ this.error(`Lost the agent-worker socket ${failures} times in a row ` +
121
+ `(${error.message}) — exiting so the service state is honest.`, { exit: 1 });
122
+ }
123
+ this.warn(`agent-worker heartbeat missed (${failures}/${HEARTBEAT_FAILURE_LIMIT})`);
124
+ }
125
+ }
126
+ // ---- bye ---------------------------------------------------------------
127
+ try {
128
+ await call('bye', BYE_TIMEOUT_MS);
129
+ }
130
+ catch {
131
+ /* a dead socket cannot hear goodbye — the missed heartbeats say it */
132
+ }
133
+ }
134
+ /** `revoked`/`invalid_handle` — the binding is gone, not the transport. */
135
+ bindingRefused(error) {
136
+ const message = error instanceof Error ? error.message : String(error);
137
+ return /revoked|invalid_handle|forbidden/i.test(message);
138
+ }
139
+ }
140
+ exports.default = AgentWorker;
@@ -163,10 +163,11 @@ class AgentsCreate extends base_command_1.BaseCommand {
163
163
  }),
164
164
  description: core_1.Flags.string({ description: 'Agent description' }),
165
165
  provider: core_1.Flags.string({
166
- description: 'Model provider (required unless set via --from-json)',
166
+ description: 'Model provider (default: agentic when --name is given)',
167
167
  }),
168
168
  model: core_1.Flags.string({
169
- description: 'Model identifier (required unless set via --from-json)',
169
+ description: 'Model identifier (default: `default`, auto-routing). On a local harness, any id the harness ' +
170
+ 'accepts — `skrr harnesses models <harness>` lists the known ones.',
170
171
  }),
171
172
  instructions: core_1.Flags.string({
172
173
  description: 'Initial raw custom instructions (creates one inline block; use `agents instructions` to compose reusable blocks later)',
@@ -199,7 +200,9 @@ class AgentsCreate extends base_command_1.BaseCommand {
199
200
  description: 'Single-purpose agent: skip the system-managed relationship portfolio (conversation continuity, relationship pulse, commitment incubator, open-loop closer). Self-reflection is still seeded.',
200
201
  }),
201
202
  'default-permission-mode': core_1.Flags.string({
202
- description: 'Default Claude permission mode for sessions',
203
+ description: "Permission mode for this agent's local-harness sessions (every harness, not only Claude Code). " +
204
+ 'One-shot harnesses (gemini, opencode, cursor, copilot, goose, pi, openclaw) can only run as ' +
205
+ 'bypassPermissions; any other mode is refused for them.',
203
206
  options: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
204
207
  }),
205
208
  tools: core_1.Flags.string({
@@ -242,19 +245,23 @@ class AgentsCreate extends base_command_1.BaseCommand {
242
245
  description: 'When this agent schedules its own work: HH:MM-HH:MM[@<IANA timezone>], e.g. ' +
243
246
  '09:00-18:00@America/Los_Angeles',
244
247
  }),
248
+ // Retired (OSK-11975): a Commitment turn works under the Agent's own
249
+ // permissions, so these three are stored but no longer read at runtime.
245
250
  'reasoning-tool-escape': core_1.Flags.string({
246
- description: 'Tool a reasoning-only turn may still use (repeatable; replaces the list). ' +
247
- 'These are ESCAPES, not a fenced tool surface.',
251
+ description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
248
252
  options: [...agent_config_1.REASONING_TOOL_ESCAPES],
249
253
  multiple: true,
254
+ deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
250
255
  }),
251
256
  'commitment-responsibility': core_1.Flags.string({
252
- description: "Default work authority for this agent's commitments",
257
+ description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
253
258
  options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
259
+ deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
254
260
  }),
255
261
  'commitment-responsibility-ceiling': core_1.Flags.string({
256
- description: 'Ceiling a commitment may not exceed, whatever it asks for',
262
+ description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
257
263
  options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
264
+ deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
258
265
  }),
259
266
  'max-turns': core_1.Flags.integer({ description: 'Maximum turns in one agent run (1-500)' }),
260
267
  'tool-resources': core_1.Flags.string({
@@ -49,7 +49,9 @@ class AgentsUpdate extends base_command_1.BaseCommand {
49
49
  description: 'Default working directory for local Harness sessions',
50
50
  }),
51
51
  'default-permission-mode': core_1.Flags.string({
52
- description: 'Default Claude permission mode for sessions',
52
+ description: "Permission mode for this agent's local-harness sessions (every harness, not only Claude Code). " +
53
+ 'One-shot harnesses (gemini, opencode, cursor, copilot, goose, pi, openclaw) can only run as ' +
54
+ 'bypassPermissions; any other mode is refused for them.',
53
55
  options: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
54
56
  }),
55
57
  tools: core_1.Flags.string({
@@ -93,19 +95,23 @@ class AgentsUpdate extends base_command_1.BaseCommand {
93
95
  description: 'When this agent schedules its own work: HH:MM-HH:MM[@<IANA timezone>], e.g. ' +
94
96
  '09:00-18:00@America/Los_Angeles',
95
97
  }),
98
+ // Retired (OSK-11975): a Commitment turn works under the Agent's own
99
+ // permissions, so these three are stored but no longer read at runtime.
96
100
  'reasoning-tool-escape': core_1.Flags.string({
97
- description: 'Tool a reasoning-only turn may still use (repeatable; replaces the list). ' +
98
- 'These are ESCAPES, not a fenced tool surface.',
101
+ description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
99
102
  options: [...agent_config_1.REASONING_TOOL_ESCAPES],
100
103
  multiple: true,
104
+ deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
101
105
  }),
102
106
  'commitment-responsibility': core_1.Flags.string({
103
- description: "Default work authority for this agent's commitments",
107
+ description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
104
108
  options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
109
+ deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
105
110
  }),
106
111
  'commitment-responsibility-ceiling': core_1.Flags.string({
107
- description: 'Ceiling a commitment may not exceed, whatever it asks for',
112
+ description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
108
113
  options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
114
+ deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
109
115
  }),
110
116
  'max-turns': core_1.Flags.integer({ description: 'Maximum turns in one agent run (1-500)' }),
111
117
  'tool-resources': core_1.Flags.string({
@@ -39,4 +39,5 @@ export default class Code extends Command {
39
39
  */
40
40
  export declare function forwardToEngine(cmd: Command, subcommand: string | null, passthrough: string[], options?: {
41
41
  managed?: boolean;
42
+ projectPolicy?: 'tui' | 'headless';
42
43
  }): Promise<void>;
@@ -6,6 +6,7 @@ const auth_core_1 = require("@skrr-ai/auth-core");
6
6
  const first_party_harness_1 = require("../../lib/first-party-harness");
7
7
  const first_party_harness_broker_1 = require("../../lib/first-party-harness-broker");
8
8
  const first_party_harness_managed_1 = require("../../lib/first-party-harness-managed");
9
+ const first_party_harness_project_trust_1 = require("../../lib/first-party-harness-project-trust");
9
10
  /**
10
11
  * `skrr code` — launch the first-party harness's interactive coding TUI in the
11
12
  * current directory.
@@ -29,7 +30,12 @@ class Code extends core_1.Command {
29
30
  static description = 'Everything after ' +
30
31
  `\`code\` is forwarded to the engine unchanged, so engine flags are not listed here — ` +
31
32
  `run \`${auth_core_1.FIRST_PARTY_HARNESS.command} run --help\` for this wrapper, or the engine's own help for its ` +
32
- `flags. ${first_party_harness_managed_1.SKRR_AGENT_FLAG} is the one flag this wrapper consumes itself.`;
33
+ `flags. This wrapper consumes three flags itself: ${first_party_harness_managed_1.SKRR_AGENT_FLAG}, ` +
34
+ `${first_party_harness_project_trust_1.TRUST_PROJECT_FLAG} (load this project's own configuration — plugins, tools, MCP ` +
35
+ `servers, permissions — which is otherwise NOT loaded until you trust the project; ` +
36
+ `the TUI asks once when a project ships any), and ${first_party_harness_project_trust_1.ENGINE_PERMISSIONS_FLAG} (let your ` +
37
+ `engine config decide permissions instead of the TUI default: reads run, edits, ` +
38
+ `commands and web access ask).`;
33
39
  static strict = false;
34
40
  static examples = [
35
41
  '<%= config.bin %> code',
@@ -37,6 +43,8 @@ class Code extends core_1.Command {
37
43
  `<%= config.bin %> code ${first_party_harness_managed_1.SKRR_AGENT_FLAG} Engineer`,
38
44
  '<%= config.bin %> code run "fix the failing test"',
39
45
  '<%= config.bin %> code doctor',
46
+ `<%= config.bin %> code ${first_party_harness_project_trust_1.TRUST_PROJECT_FLAG}`,
47
+ `<%= config.bin %> code ${first_party_harness_project_trust_1.ENGINE_PERMISSIONS_FLAG}`,
40
48
  ];
41
49
  async run() {
42
50
  // `this.argv`, NOT `this.parse()`. oclif's `strict = false` relaxes POSITIONAL
@@ -52,7 +60,7 @@ class Code extends core_1.Command {
52
60
  //
53
61
  // The unit tests did not catch it because they cover `buildEngineArgs`, which
54
62
  // always forwarded verbatim. The args never reached it.
55
- await forwardToEngine(this, null, this.argv, { managed: true });
63
+ await forwardToEngine(this, null, this.argv, { managed: true, projectPolicy: 'tui' });
56
64
  }
57
65
  }
58
66
  exports.default = Code;
@@ -79,7 +87,8 @@ exports.default = Code;
79
87
  async function forwardToEngine(cmd, subcommand, passthrough, options = {}) {
80
88
  // Unconditional: the engine must never see a flag it does not declare, whether
81
89
  // or not this run brokers. `skrr code --agent x` remains the ENGINE's flag.
82
- const { agentRef, engineArgv } = (0, first_party_harness_managed_1.takeOverskyAgentFlag)(passthrough);
90
+ const policyFlags = (0, first_party_harness_project_trust_1.takeProjectPolicyFlags)(passthrough);
91
+ const { agentRef, engineArgv } = (0, first_party_harness_managed_1.takeOverskyAgentFlag)(policyFlags.engineArgv);
83
92
  // `logToStderr`, never `log`: `skrr code run --json` writes machine-readable
84
93
  // output on stdout, and one friendly sentence in the middle of it is a parse
85
94
  // error at the other end.
@@ -88,6 +97,29 @@ async function forwardToEngine(cmd, subcommand, passthrough, options = {}) {
88
97
  say(`${first_party_harness_managed_1.SKRR_AGENT_FLAG} has no effect here — this command does not host a managed ` +
89
98
  'session. It was removed from the arguments passed to the engine.');
90
99
  }
100
+ // OSK-11958 — the project's own configuration can run code on this machine
101
+ // and loosen permissions, so it is not loaded until the person trusts the
102
+ // project. Decided BEFORE any lease is acquired: a refusal to answer must not
103
+ // cost a broker. Only the TUI and `run` — an engine subcommand such as
104
+ // `trust` must see the project config it exists to approve, and `acp` is
105
+ // the daemon's, which sets its own.
106
+ const projectDir = options.projectPolicy === 'tui'
107
+ ? (0, first_party_harness_project_trust_1.tuiProjectDir)(engineArgv, process.cwd())
108
+ : options.projectPolicy === 'headless'
109
+ ? process.cwd()
110
+ : null;
111
+ const policy = options.projectPolicy && projectDir
112
+ ? await (0, first_party_harness_project_trust_1.resolveProjectPolicy)({
113
+ cwd: projectDir,
114
+ env: process.env,
115
+ mode: options.projectPolicy,
116
+ trustProject: policyFlags.trustProject,
117
+ enginePermissions: policyFlags.enginePermissions,
118
+ ask: options.projectPolicy === 'tui' ? (0, first_party_harness_project_trust_1.ttyTrustAsker)() : undefined,
119
+ })
120
+ : null;
121
+ for (const notice of policy?.notices ?? [])
122
+ say(notice);
91
123
  const managed = options.managed
92
124
  ? (0, first_party_harness_broker_1.createManagedEngineHooks)({
93
125
  subcommand,
@@ -106,10 +138,22 @@ async function forwardToEngine(cmd, subcommand, passthrough, options = {}) {
106
138
  // skipped or failed attempt is a genuine BYOK run whose credential is
107
139
  // the ambient one. Only the hooks know which happened.
108
140
  credentialMode: managed.credentialMode,
141
+ policyEnv: policy?.env,
142
+ }
143
+ : { policyEnv: policy?.env });
144
+ if (code !== 0) {
145
+ // The engine ran with no managed session and no credential to inherit —
146
+ // its failure (typically a bare "Unexpected server error") names nothing
147
+ // the user can act on. The prepare-time warning already diagnosed this;
148
+ // say it again where the failure lands, because a warning scrolled past
149
+ // is not a diagnosis at the point of failure (OSK-11949).
150
+ if (managed?.unprovisionedByok()) {
151
+ say('No managed session and no provider credential reached the engine — if ' +
152
+ 'the failure above is an authentication or server error, sign in with ' +
153
+ '`skrr login` or set OPENROUTER_API_KEY and retry.');
109
154
  }
110
- : {});
111
- if (code !== 0)
112
155
  cmd.exit(code);
156
+ }
113
157
  }
114
158
  catch (err) {
115
159
  if (err instanceof first_party_harness_1.EngineStartRefusedError) {
@@ -16,6 +16,7 @@ class CodeRun extends core_1.Command {
16
16
  '<%= config.bin %> code run "add a regression test for the parser"',
17
17
  '<%= config.bin %> code run --json "summarize the diff"',
18
18
  '<%= config.bin %> code run --oversky-agent Engineer "fix the failing test"',
19
+ '<%= config.bin %> code run --trust-project "run the project\'s own lint plugin"',
19
20
  ];
20
21
  async run() {
21
22
  // `this.argv`, NOT `this.parse()`. oclif's `strict = false` relaxes POSITIONAL
@@ -31,7 +32,10 @@ class CodeRun extends core_1.Command {
31
32
  //
32
33
  // The unit tests did not catch it because they cover `buildEngineArgs`, which
33
34
  // always forwarded verbatim. The args never reached it.
34
- await (0, index_1.forwardToEngine)(this, 'run', this.argv, { managed: true });
35
+ // `headless`: project configuration is gated exactly as for the TUI, but
36
+ // nobody is asked and the engine keeps its own permission config — an `ask`
37
+ // default would be auto-rejected by a headless run (OSK-11958).
38
+ await (0, index_1.forwardToEngine)(this, 'run', this.argv, { managed: true, projectPolicy: 'headless' });
35
39
  }
36
40
  }
37
41
  exports.default = CodeRun;
@@ -0,0 +1,19 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * The Cycle is the unit a person reads: one row per check (and per wake that
4
+ * never became one), each with one outcome and, when it stopped, the one
5
+ * reason and what to do about it.
6
+ */
7
+ export default class CommitmentsCycles extends BaseCommand {
8
+ static description: string;
9
+ static examples: string[];
10
+ static args: {
11
+ commitmentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
12
+ };
13
+ static flags: {
14
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
16
+ cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
+ };
18
+ run(): Promise<void>;
19
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../base-command");
5
+ const commitments_1 = require("../../lib/commitments");
6
+ /**
7
+ * The Cycle is the unit a person reads: one row per check (and per wake that
8
+ * never became one), each with one outcome and, when it stopped, the one
9
+ * reason and what to do about it.
10
+ */
11
+ class CommitmentsCycles extends base_command_1.BaseCommand {
12
+ static description = "List a commitment's cycles, newest first: what woke it, how each ended, and why it stopped";
13
+ static examples = [
14
+ '<%= config.bin %> commitments cycles obj_abc123',
15
+ '<%= config.bin %> commitments cycles obj_abc123 --limit 50 --json',
16
+ '<%= config.bin %> commitments cycles obj_abc123 --cursor <next_cursor>',
17
+ ];
18
+ static args = {
19
+ commitmentId: core_1.Args.string({ ignoreStdin: true, description: 'Commitment ID', required: true }),
20
+ };
21
+ static flags = {
22
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
23
+ limit: core_1.Flags.integer({ description: 'Max checks per page (1-50)', min: 1, max: 50 }),
24
+ cursor: core_1.Flags.string({ description: 'Opaque cursor returned by the preceding page' }),
25
+ };
26
+ async run() {
27
+ this.requireAuth();
28
+ const { args, flags } = await this.parse(CommitmentsCycles);
29
+ try {
30
+ const page = await commitments_1.commitmentApi.cycles(args.commitmentId, {
31
+ ...(typeof flags.limit === 'number' ? { limit: flags.limit } : {}),
32
+ ...(flags.cursor ? { cursor: flags.cursor } : {}),
33
+ });
34
+ if (flags.json) {
35
+ this.log(JSON.stringify(page, null, 2));
36
+ return;
37
+ }
38
+ for (const line of (0, commitments_1.formatCommitmentCycles)(page))
39
+ this.log(line);
40
+ }
41
+ catch (err) {
42
+ this.handleApiError(err);
43
+ }
44
+ }
45
+ }
46
+ exports.default = CommitmentsCycles;
@@ -5,6 +5,12 @@ const core_1 = require("@oclif/core");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const commitments_1 = require("../../lib/commitments");
7
7
  const commitment_product_1 = require("../../lib/commitment-product");
8
+ /** Which limit binds the daily action runs — the fix lives on that object. */
9
+ const RUN_LIMIT_BINDING_LABELS = {
10
+ northstar_allocation: 'Northstar allocation',
11
+ workspace_admin: 'workspace limit',
12
+ owner: 'owner limit',
13
+ };
8
14
  function effectivePolicyLines(policy) {
9
15
  const governance = policy.governance || {};
10
16
  const owner = policy.owner || {};
@@ -28,17 +34,36 @@ function effectivePolicyLines(policy) {
28
34
  if (Array.isArray(governance.allowedCommitmentDeliveryModes)) {
29
35
  lines.push(`Workspace delivery allowlist: ${governance.allowedCommitmentDeliveryModes.length ? governance.allowedCommitmentDeliveryModes.map(commitment_product_1.deliveryLabel).join(', ') : 'none'}`);
30
36
  }
31
- if (Array.isArray(governance.requireAttestedAccessForDeliveryModes) && governance.requireAttestedAccessForDeliveryModes.length) {
37
+ if (Array.isArray(governance.requireAttestedAccessForDeliveryModes) &&
38
+ governance.requireAttestedAccessForDeliveryModes.length) {
32
39
  lines.push(`Runtime attestation required for: ${governance.requireAttestedAccessForDeliveryModes.map(commitment_product_1.deliveryLabel).join(', ')}`);
33
40
  }
34
41
  const runLimit = owner.actionRunsPerDay;
35
42
  if (runLimit) {
36
43
  lines.push(`Owner action-run limit: ${runLimit.mode === 'unbounded' ? 'Unlimited' : `${runLimit.value}/day`}`);
37
44
  }
45
+ const effectiveRuns = policy.effective?.actionRunsPerDay;
46
+ if (effectiveRuns) {
47
+ const bindingLabel = RUN_LIMIT_BINDING_LABELS[effectiveRuns.binding];
48
+ const binding = bindingLabel ? ` (binding: ${bindingLabel})` : '';
49
+ lines.push(`Effective action-run limit: ${effectiveRuns.mode === 'unbounded' ? 'Unlimited' : `${effectiveRuns.value}/day`}${binding}`);
50
+ }
38
51
  const cost = policy.billing;
39
52
  if (cost?.effectiveCostLimitUsd != null) {
40
53
  lines.push(`Effective daily cost limit: $${Number(cost.effectiveCostLimitUsd).toFixed(2)}`);
41
54
  }
55
+ // What a sensing turn's fence holds (OSK-11978). The shell line carries the
56
+ // part that is NOT enforced, because that is the one an operator granting
57
+ // it has to see.
58
+ const observation = policy.observation;
59
+ if (observation?.sensing) {
60
+ lines.push(`Sensing shell: ${observation.shell?.granted
61
+ ? 'granted — read-only filesystem on the Hosted Machine; network NOT fenced (a command can still change remote state); withheld on your own machine'
62
+ : 'not granted'}`);
63
+ lines.push(`Sensing browser: ${observation.browser?.granted
64
+ ? 'granted — read-only, enforced by your machine'
65
+ : 'not granted'}`);
66
+ }
42
67
  return lines;
43
68
  }
44
69
  /** Show the policy actually governing a Commitment without changing it. */
@@ -0,0 +1,17 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * Walk the latest cycle as six questions — did anything wake it, was there new
4
+ * evidence, was it its turn, did the agent think, did it act, did it tell you —
5
+ * and name the first "no" with its fix.
6
+ */
7
+ export default class CommitmentsExplain extends BaseCommand {
8
+ static description: string;
9
+ static examples: string[];
10
+ static args: {
11
+ commitmentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
12
+ };
13
+ static flags: {
14
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../base-command");
5
+ const commitments_1 = require("../../lib/commitments");
6
+ /**
7
+ * Walk the latest cycle as six questions — did anything wake it, was there new
8
+ * evidence, was it its turn, did the agent think, did it act, did it tell you —
9
+ * and name the first "no" with its fix.
10
+ */
11
+ class CommitmentsExplain extends base_command_1.BaseCommand {
12
+ static description = 'Explain where the latest cycle of a commitment stopped, as six yes/no questions';
13
+ static examples = [
14
+ '<%= config.bin %> commitments explain obj_abc123',
15
+ '<%= config.bin %> commitments explain obj_abc123 --json',
16
+ ];
17
+ static args = {
18
+ commitmentId: core_1.Args.string({ ignoreStdin: true, description: 'Commitment ID', required: true }),
19
+ };
20
+ static flags = {
21
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
22
+ };
23
+ async run() {
24
+ this.requireAuth();
25
+ const { args, flags } = await this.parse(CommitmentsExplain);
26
+ try {
27
+ const explanation = await commitments_1.commitmentApi.explain(args.commitmentId);
28
+ if (flags.json) {
29
+ this.log(JSON.stringify(explanation, null, 2));
30
+ return;
31
+ }
32
+ for (const line of (0, commitments_1.formatCommitmentExplanation)(explanation))
33
+ this.log(line);
34
+ }
35
+ catch (err) {
36
+ this.handleApiError(err);
37
+ }
38
+ }
39
+ }
40
+ exports.default = CommitmentsExplain;
@@ -10,6 +10,7 @@ export default class GoalsCreate extends BaseCommand {
10
10
  level: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
11
  'target-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
12
  space: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ label: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
14
  project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
14
15
  parent: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
15
16
  'owner-agent': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;