@skrr-ai/cli 0.1.86 → 0.1.88
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/bin/run.js +11 -0
- package/dist/base-command.js +1 -0
- package/dist/commands/agents/chat.d.ts +58 -1
- package/dist/commands/agents/chat.js +206 -4
- package/dist/commands/computer/index.d.ts +19 -0
- package/dist/commands/computer/index.js +37 -0
- package/dist/commands/labels/create.d.ts +1 -0
- package/dist/commands/labels/create.js +2 -0
- package/dist/commands/labels/restore.d.ts +1 -0
- package/dist/commands/labels/restore.js +6 -3
- package/dist/commands/labels/update.d.ts +1 -0
- package/dist/commands/labels/update.js +2 -0
- package/dist/commands/machines/computer-adoption.d.ts +15 -0
- package/dist/commands/machines/computer-adoption.js +33 -0
- package/dist/commands/machines/dedicated/archive.d.ts +26 -0
- package/dist/commands/machines/dedicated/archive.js +113 -0
- package/dist/commands/machines/dedicated/audit.d.ts +25 -0
- package/dist/commands/machines/dedicated/audit.js +113 -0
- package/dist/commands/machines/dedicated/computer.d.ts +23 -0
- package/dist/commands/machines/dedicated/computer.js +64 -0
- package/dist/commands/machines/dedicated/cp.d.ts +2 -0
- package/dist/commands/machines/dedicated/cp.js +74 -2
- package/dist/commands/machines/dedicated/git-status.d.ts +15 -0
- package/dist/commands/machines/dedicated/git-status.js +56 -0
- package/dist/commands/machines/dedicated/index.js +15 -1
- package/dist/commands/machines/dedicated/ls.d.ts +27 -0
- package/dist/commands/machines/dedicated/ls.js +100 -0
- package/dist/commands/machines/dedicated/mkdir.d.ts +18 -0
- package/dist/commands/machines/dedicated/mkdir.js +57 -0
- package/dist/commands/machines/dedicated/mv.d.ts +24 -0
- package/dist/commands/machines/dedicated/mv.js +83 -0
- package/dist/commands/machines/dedicated/rm.d.ts +26 -0
- package/dist/commands/machines/dedicated/rm.js +100 -0
- package/dist/commands/machines/dedicated/search.d.ts +24 -0
- package/dist/commands/machines/dedicated/search.js +78 -0
- package/dist/commands/machines/dedicated/stat.d.ts +22 -0
- package/dist/commands/machines/dedicated/stat.js +92 -0
- package/dist/commands/machines/hosted/index.js +1 -1
- package/dist/commands/machines/hosted/list.js +1 -1
- package/dist/commands/machines/hosted/start.js +1 -1
- package/dist/commands/machines/services/declare.d.ts +24 -0
- package/dist/commands/machines/services/declare.js +73 -0
- package/dist/commands/machines/services/index.d.ts +15 -0
- package/dist/commands/machines/services/index.js +31 -0
- package/dist/commands/machines/services/ls.d.ts +17 -0
- package/dist/commands/machines/services/ls.js +60 -0
- package/dist/commands/machines/services/withdraw.d.ts +19 -0
- package/dist/commands/machines/services/withdraw.js +47 -0
- package/dist/commands/machines/share.d.ts +29 -0
- package/dist/commands/machines/share.js +100 -0
- package/dist/commands/machines/shared.d.ts +19 -0
- package/dist/commands/machines/shared.js +62 -0
- package/dist/commands/machines/shares.d.ts +16 -0
- package/dist/commands/machines/shares.js +69 -0
- package/dist/commands/machines/unshare.d.ts +19 -0
- package/dist/commands/machines/unshare.js +61 -0
- package/dist/commands/tasks/labels/create.d.ts +1 -0
- package/dist/commands/tasks/labels/create.js +3 -0
- package/dist/commands/views/create.d.ts +1 -0
- package/dist/commands/views/create.js +5 -0
- package/dist/lib/agent-home-workspace.d.ts +26 -0
- package/dist/lib/agent-home-workspace.js +174 -0
- package/dist/lib/agentic-stream.d.ts +135 -0
- package/dist/lib/agentic-stream.js +341 -6
- package/dist/lib/auth-storage.d.ts +4 -0
- package/dist/lib/auth-storage.js +33 -0
- package/dist/lib/computer-adoption.d.ts +55 -0
- package/dist/lib/computer-adoption.js +67 -0
- package/dist/lib/computer-consent.d.ts +13 -0
- package/dist/lib/computer-consent.js +33 -0
- package/dist/lib/computer-files.d.ts +239 -0
- package/dist/lib/computer-files.js +707 -0
- package/dist/lib/dedicated-copy.d.ts +45 -9
- package/dist/lib/dedicated-copy.js +141 -44
- package/dist/lib/dedicated-machines.d.ts +49 -0
- package/dist/lib/dedicated-machines.js +97 -6
- package/dist/lib/keychain.d.ts +1 -0
- package/dist/lib/keychain.js +67 -14
- package/dist/lib/label-scope.d.ts +12 -0
- package/dist/lib/label-scope.js +15 -1
- package/dist/lib/machine-audit.d.ts +34 -0
- package/dist/lib/machine-audit.js +50 -0
- package/dist/lib/machine-grants.d.ts +70 -0
- package/dist/lib/machine-grants.js +47 -0
- package/dist/lib/machine-services.d.ts +64 -0
- package/dist/lib/machine-services.js +60 -0
- package/dist/lib/refresh.d.ts +7 -0
- package/dist/lib/refresh.js +2 -1
- package/dist/lib/task-view-render.d.ts +2 -0
- package/dist/lib/task-view-render.js +1 -1
- package/dist/lib/views/vocabulary.d.ts +1 -1
- package/dist/lib/views/vocabulary.js +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +955 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +1070 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +7 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +955 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +1051 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +7 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +22143 -20728
- package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
- package/oclif.manifest.json +18855 -17260
- package/package.json +2 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AgenticStreamClient = exports.USER_CONNECTION_CAP_CODE = exports.CODEX_DECLINED_TOOL_OUTPUTS = exports.TYPE_AHEAD_DISCARD_MS = exports.NON_INTERACTIVE_QUESTION_DISMISSAL = void 0;
|
|
3
|
+
exports.AgenticStreamClient = exports.USER_CONNECTION_CAP_CODE = exports.CODEX_DECLINED_TOOL_OUTPUTS = exports.TYPE_AHEAD_DISCARD_MS = exports.HANDOVER_POLL_INTERVAL_MS = exports.DAEMON_HANDOVER_REASON = exports.WORKER_REPLACED_CODE = exports.NON_INTERACTIVE_QUESTION_DISMISSAL = void 0;
|
|
4
|
+
exports.isDaemonHandoverFailure = isDaemonHandoverFailure;
|
|
5
|
+
exports.formatWaitDuration = formatWaitDuration;
|
|
4
6
|
exports.writeJsonLine = writeJsonLine;
|
|
5
7
|
exports.discardTypeAhead = discardTypeAhead;
|
|
6
8
|
exports.describePermissionTarget = describePermissionTarget;
|
|
@@ -41,6 +43,54 @@ const dpop_auth_1 = require("./dpop-auth");
|
|
|
41
43
|
*/
|
|
42
44
|
exports.NON_INTERACTIVE_QUESTION_DISMISSAL = 'No answer: this turn was started non-interactively (no terminal), so nobody can reply to ' +
|
|
43
45
|
'questions. Proceed with your best judgement, or finish by stating exactly what you need.';
|
|
46
|
+
/**
|
|
47
|
+
* OSK-13517 / OSK-13543 — the one failure that is not an ending.
|
|
48
|
+
*
|
|
49
|
+
* A deploy replaced the server RELAYING a local-daemon turn while the daemon
|
|
50
|
+
* kept running it. The server saves the turn `error: true` with this reason
|
|
51
|
+
* (`metadata.agentic.errorSubtype`), keeps the conversation quarantined, and
|
|
52
|
+
* converges the row later from the machine's own turn-end: completed →
|
|
53
|
+
* repaired (`error: false`, `metadata.agentic.deliveryRepair`), anything else →
|
|
54
|
+
* settled failed (`errorSubtype: worker_replaced_daemon_failed`).
|
|
55
|
+
*
|
|
56
|
+
* Mirrors `WORKER_REPLACED_DAEMON_RUNNING_REASON` in
|
|
57
|
+
* `api/server/services/AgentWorker/workerReplacedNotice.js` and the code
|
|
58
|
+
* beside it in `daemonHandlers.js` / `conversationAdmissionClaim.js`; a parity
|
|
59
|
+
* spec reads those files so the two cannot drift.
|
|
60
|
+
*/
|
|
61
|
+
exports.WORKER_REPLACED_CODE = 'AGENT_WORKER_REPLACED';
|
|
62
|
+
exports.DAEMON_HANDOVER_REASON = 'worker_replaced_daemon_running';
|
|
63
|
+
/**
|
|
64
|
+
* Is this terminal event the hand-over failure, and nothing else?
|
|
65
|
+
*
|
|
66
|
+
* The server sends it on two paths with different shapes, so the reason is
|
|
67
|
+
* read wherever either puts it:
|
|
68
|
+
* - the QueueEvents backstop (`queue.js`): `{code: 'AGENT_WORKER_REPLACED',
|
|
69
|
+
* errorType: 'worker_replaced_daemon_running', errors}`;
|
|
70
|
+
* - the worker's own result (`buildExecutionErrorEvent`): `errorType` and
|
|
71
|
+
* `reason` both carry the daemon reason, `code` only when the result had one.
|
|
72
|
+
* The plain deploy failure shares the CODE (`errorType: 'worker_replaced'`, the
|
|
73
|
+
* turn really ended), so the code alone must never qualify, and a code that
|
|
74
|
+
* is present but different disqualifies.
|
|
75
|
+
*/
|
|
76
|
+
function isDaemonHandoverFailure(event) {
|
|
77
|
+
if (!event)
|
|
78
|
+
return false;
|
|
79
|
+
if (event.code !== undefined && event.code !== exports.WORKER_REPLACED_CODE)
|
|
80
|
+
return false;
|
|
81
|
+
return [event.errorType, event.reason, event.daemonReason].includes(exports.DAEMON_HANDOVER_REASON);
|
|
82
|
+
}
|
|
83
|
+
/** Default fallback re-read cadence while waiting on a handed-over turn. */
|
|
84
|
+
exports.HANDOVER_POLL_INTERVAL_MS = 15_000;
|
|
85
|
+
/** `600000` → `10m`, `90000` → `1m30s`, `45000` → `45s`. */
|
|
86
|
+
function formatWaitDuration(ms) {
|
|
87
|
+
const total = Math.max(0, Math.round(ms / 1000));
|
|
88
|
+
const minutes = Math.floor(total / 60);
|
|
89
|
+
const seconds = total % 60;
|
|
90
|
+
if (!minutes)
|
|
91
|
+
return `${seconds}s`;
|
|
92
|
+
return seconds ? `${minutes}m${seconds}s` : `${minutes}m`;
|
|
93
|
+
}
|
|
44
94
|
/**
|
|
45
95
|
* Write exactly one complete JSON value on one line.
|
|
46
96
|
*
|
|
@@ -745,6 +795,11 @@ class AgenticStreamClient {
|
|
|
745
795
|
* a reconciled detach report the real outcome instead of the detach.
|
|
746
796
|
*/
|
|
747
797
|
terminalResult = null;
|
|
798
|
+
/**
|
|
799
|
+
* A handed-over turn being waited on (OSK-13543): the failure event that
|
|
800
|
+
* started the wait, and the timers bounding it. Null otherwise.
|
|
801
|
+
*/
|
|
802
|
+
handover = null;
|
|
748
803
|
resolve;
|
|
749
804
|
reject;
|
|
750
805
|
/** The terminal result, or null while the run is still in flight. */
|
|
@@ -833,6 +888,11 @@ class AgenticStreamClient {
|
|
|
833
888
|
return;
|
|
834
889
|
if (this.settled || this.closed)
|
|
835
890
|
return;
|
|
891
|
+
// While a handed-over turn is waited on, the record reads `error: true` BY
|
|
892
|
+
// DESIGN — it is the failure being waited out. The generic probe would
|
|
893
|
+
// settle on exactly that; the hand-over reader owns the record instead.
|
|
894
|
+
if (this.handover)
|
|
895
|
+
return;
|
|
836
896
|
if (this.idleTimer)
|
|
837
897
|
clearTimeout(this.idleTimer);
|
|
838
898
|
this.idleTimer = setTimeout(() => void this.probeTerminal('idle'), this.options.idleTerminalProbeMs ?? 20_000);
|
|
@@ -846,7 +906,7 @@ class AgenticStreamClient {
|
|
|
846
906
|
*/
|
|
847
907
|
async probeTerminal(reason) {
|
|
848
908
|
const reconcile = this.options.reconcileTerminal;
|
|
849
|
-
if (!reconcile || this.settled || this.reconciling)
|
|
909
|
+
if (!reconcile || this.settled || this.reconciling || this.handover)
|
|
850
910
|
return false;
|
|
851
911
|
this.reconciling = true;
|
|
852
912
|
let terminal = null;
|
|
@@ -866,6 +926,23 @@ class AgenticStreamClient {
|
|
|
866
926
|
this.armIdleProbe();
|
|
867
927
|
return false;
|
|
868
928
|
}
|
|
929
|
+
// The stream never delivered the hand-over failure (joined late, or the
|
|
930
|
+
// frame was lost), but the record carries it: wait the same way. Not on a
|
|
931
|
+
// detach — the caller has already stopped watching and asks only what the
|
|
932
|
+
// record says now.
|
|
933
|
+
if (terminal.handedOver && reason !== 'detach') {
|
|
934
|
+
const begun = this.beginHandoverWait({
|
|
935
|
+
type: 'execution:error',
|
|
936
|
+
sessionId: this.options.sessionId,
|
|
937
|
+
code: exports.WORKER_REPLACED_CODE,
|
|
938
|
+
errorType: exports.DAEMON_HANDOVER_REASON,
|
|
939
|
+
reason: exports.DAEMON_HANDOVER_REASON,
|
|
940
|
+
reconciledFrom: 'durable_record',
|
|
941
|
+
...(terminal.error ? { message: terminal.error, errors: [terminal.error] } : {}),
|
|
942
|
+
});
|
|
943
|
+
if (begun)
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
869
946
|
// The record is the witness the stream refused to be, so say so: the
|
|
870
947
|
// recorded reason is exactly what "the surface shows nothing at all" cost
|
|
871
948
|
// the reader (OSK-7595 — the CLI reported a transport error while the run's
|
|
@@ -883,6 +960,11 @@ class AgenticStreamClient {
|
|
|
883
960
|
cancel(reason = 'Interrupted by user') {
|
|
884
961
|
if (this.settled || !this.socket)
|
|
885
962
|
return;
|
|
963
|
+
// A handed-over turn has no relaying server left to carry an interrupt to
|
|
964
|
+
// the machine, so sending one would claim a cancel nothing performs. The
|
|
965
|
+
// caller stops WAITING instead ({@link awaitingHandover}).
|
|
966
|
+
if (this.handover)
|
|
967
|
+
return;
|
|
886
968
|
this.cancelled = true;
|
|
887
969
|
this.socket.emit('execution:interrupt', {
|
|
888
970
|
sessionId: this.options.sessionId,
|
|
@@ -902,6 +984,7 @@ class AgenticStreamClient {
|
|
|
902
984
|
if (this.replayTimer)
|
|
903
985
|
clearTimeout(this.replayTimer);
|
|
904
986
|
this.replayTimer = null;
|
|
987
|
+
this.stopHandoverTimers();
|
|
905
988
|
this.pendingEnvelopes.clear();
|
|
906
989
|
this.abandonConnectWait?.();
|
|
907
990
|
const socket = this.socket;
|
|
@@ -923,6 +1006,189 @@ class AgenticStreamClient {
|
|
|
923
1006
|
this.readline?.close();
|
|
924
1007
|
this.readline = null;
|
|
925
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* True while a handed-over turn is being waited on (OSK-13543). A Ctrl-C in
|
|
1011
|
+
* this state stops the wait; it cannot cancel the machine's turn.
|
|
1012
|
+
*/
|
|
1013
|
+
awaitingHandover() {
|
|
1014
|
+
return this.handover !== null && !this.settled && !this.closed;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* OSK-13543 — the run failed because a deploy replaced the server relaying a
|
|
1018
|
+
* turn the machine is still running. That failure is the server's PROVISIONAL
|
|
1019
|
+
* verdict: it keeps the conversation held and rewrites the row from the
|
|
1020
|
+
* machine's own turn-end. Ending here told a CLI user "failed" about a turn
|
|
1021
|
+
* whose answer the web showed ninety seconds later.
|
|
1022
|
+
*
|
|
1023
|
+
* So keep the stream, the user-room subscription and the envelope replay
|
|
1024
|
+
* open, and settle on what the RECORD converges to: repaired → completed,
|
|
1025
|
+
* settled failed → failed with the machine's reason, neither within the
|
|
1026
|
+
* bound → the original failure. The record, not the notification, decides:
|
|
1027
|
+
* a notification only says "read again", so a lost or duplicated one costs
|
|
1028
|
+
* at most a poll interval.
|
|
1029
|
+
*
|
|
1030
|
+
* Returns false (and changes nothing) when the run cannot wait: no hand-over
|
|
1031
|
+
* reader configured, a zero bound, or a run already settling.
|
|
1032
|
+
*/
|
|
1033
|
+
beginHandoverWait(event) {
|
|
1034
|
+
const opts = this.options.awaitHandover;
|
|
1035
|
+
if (!opts || !(opts.maxWaitMs > 0))
|
|
1036
|
+
return false;
|
|
1037
|
+
if (this.settled || this.closed || this.finishingReplay || this.cancelled)
|
|
1038
|
+
return false;
|
|
1039
|
+
if (this.handover)
|
|
1040
|
+
return true;
|
|
1041
|
+
if (this.idleTimer) {
|
|
1042
|
+
clearTimeout(this.idleTimer);
|
|
1043
|
+
this.idleTimer = null;
|
|
1044
|
+
}
|
|
1045
|
+
const startedAt = Date.now();
|
|
1046
|
+
// Deliberately NOT unref'd, for the reason `armIdleProbe` gives: an
|
|
1047
|
+
// unsettled run is a reason to stay alive (OSK-10061).
|
|
1048
|
+
const deadlineTimer = setTimeout(() => void this.expireHandover(), opts.maxWaitMs);
|
|
1049
|
+
const pollTimer = setInterval(() => this.requestHandoverRead(), opts.pollIntervalMs ?? exports.HANDOVER_POLL_INTERVAL_MS);
|
|
1050
|
+
this.handover = { event, startedAt, deadlineTimer, pollTimer, reading: null, readAgain: false };
|
|
1051
|
+
const firstError = Array.isArray(event.errors) && typeof event.errors[0] === 'string'
|
|
1052
|
+
? event.errors[0]
|
|
1053
|
+
: undefined;
|
|
1054
|
+
const message = typeof event.message === 'string' && event.message ? event.message : firstError;
|
|
1055
|
+
this.emit({
|
|
1056
|
+
type: 'run.handed_over',
|
|
1057
|
+
sessionId: this.options.sessionId,
|
|
1058
|
+
code: exports.WORKER_REPLACED_CODE,
|
|
1059
|
+
reason: exports.DAEMON_HANDOVER_REASON,
|
|
1060
|
+
...(message ? { message } : {}),
|
|
1061
|
+
...(opts.responseMessageId ? { responseMessageId: opts.responseMessageId } : {}),
|
|
1062
|
+
waitMs: opts.maxWaitMs,
|
|
1063
|
+
deadlineAt: new Date(startedAt + opts.maxWaitMs).toISOString(),
|
|
1064
|
+
});
|
|
1065
|
+
// The repair can land before the failure notice reaches us (the queue's
|
|
1066
|
+
// backstop records and settles in one pass), so look once right away.
|
|
1067
|
+
this.requestHandoverRead();
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
1070
|
+
/** Ask the record again; coalesces with a read already in flight. */
|
|
1071
|
+
requestHandoverRead() {
|
|
1072
|
+
const handover = this.handover;
|
|
1073
|
+
if (!handover || this.settled || this.closed)
|
|
1074
|
+
return;
|
|
1075
|
+
if (handover.reading) {
|
|
1076
|
+
handover.readAgain = true;
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
handover.reading = this.readHandover().finally(() => {
|
|
1080
|
+
if (this.handover !== handover)
|
|
1081
|
+
return;
|
|
1082
|
+
handover.reading = null;
|
|
1083
|
+
if (handover.readAgain) {
|
|
1084
|
+
handover.readAgain = false;
|
|
1085
|
+
this.requestHandoverRead();
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
/** One read of the record. True when it settled the run. */
|
|
1090
|
+
async readHandover() {
|
|
1091
|
+
const handover = this.handover;
|
|
1092
|
+
const read = this.options.awaitHandover?.read;
|
|
1093
|
+
if (!handover || !read)
|
|
1094
|
+
return false;
|
|
1095
|
+
let reading = null;
|
|
1096
|
+
try {
|
|
1097
|
+
reading = await read();
|
|
1098
|
+
}
|
|
1099
|
+
catch {
|
|
1100
|
+
// A failed read is no evidence about the turn; the next trigger retries.
|
|
1101
|
+
reading = null;
|
|
1102
|
+
}
|
|
1103
|
+
if (this.handover !== handover || this.settled || this.closed)
|
|
1104
|
+
return false;
|
|
1105
|
+
if (!reading || reading.state === 'pending')
|
|
1106
|
+
return false;
|
|
1107
|
+
const waitedMs = Date.now() - handover.startedAt;
|
|
1108
|
+
this.endHandover();
|
|
1109
|
+
if (reading.state === 'repaired') {
|
|
1110
|
+
this.finish('completed', {
|
|
1111
|
+
type: 'execution:complete',
|
|
1112
|
+
sessionId: this.options.sessionId,
|
|
1113
|
+
result: reading.text,
|
|
1114
|
+
repaired: true,
|
|
1115
|
+
repairedFrom: 'durable_record',
|
|
1116
|
+
overturned: { code: exports.WORKER_REPLACED_CODE, reason: exports.DAEMON_HANDOVER_REASON },
|
|
1117
|
+
waitedMs,
|
|
1118
|
+
...(reading.repairedAt ? { repairedAt: reading.repairedAt } : {}),
|
|
1119
|
+
});
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1122
|
+
// The machine reported its own ending and the server settled the row on
|
|
1123
|
+
// it. That sentence replaces the provisional "still working" one, which is
|
|
1124
|
+
// now false; the original fields stay so a consumer keyed on them still
|
|
1125
|
+
// matches.
|
|
1126
|
+
this.finish('failed', {
|
|
1127
|
+
...handover.event,
|
|
1128
|
+
message: reading.error,
|
|
1129
|
+
errors: [reading.error],
|
|
1130
|
+
...(typeof reading.text === 'string' && reading.text ? { result: reading.text } : {}),
|
|
1131
|
+
handover: { outcome: 'machine_failed', waitedMs },
|
|
1132
|
+
});
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
/** The bound ran out: one last look, then the original failure. */
|
|
1136
|
+
async expireHandover() {
|
|
1137
|
+
const handover = this.handover;
|
|
1138
|
+
if (!handover || this.settled || this.closed)
|
|
1139
|
+
return;
|
|
1140
|
+
if (handover.pollTimer)
|
|
1141
|
+
clearInterval(handover.pollTimer);
|
|
1142
|
+
handover.pollTimer = null;
|
|
1143
|
+
handover.deadlineTimer = null;
|
|
1144
|
+
if (handover.reading)
|
|
1145
|
+
await handover.reading;
|
|
1146
|
+
if (this.handover !== handover || this.settled || this.closed)
|
|
1147
|
+
return;
|
|
1148
|
+
if (await this.readHandover())
|
|
1149
|
+
return;
|
|
1150
|
+
if (this.handover !== handover || this.settled || this.closed)
|
|
1151
|
+
return;
|
|
1152
|
+
const waitedMs = Date.now() - handover.startedAt;
|
|
1153
|
+
this.endHandover();
|
|
1154
|
+
this.finish('failed', { ...handover.event, handover: { outcome: 'expired', waitedMs } });
|
|
1155
|
+
}
|
|
1156
|
+
stopHandoverTimers() {
|
|
1157
|
+
if (!this.handover)
|
|
1158
|
+
return;
|
|
1159
|
+
if (this.handover.deadlineTimer)
|
|
1160
|
+
clearTimeout(this.handover.deadlineTimer);
|
|
1161
|
+
if (this.handover.pollTimer)
|
|
1162
|
+
clearInterval(this.handover.pollTimer);
|
|
1163
|
+
this.handover.deadlineTimer = null;
|
|
1164
|
+
this.handover.pollTimer = null;
|
|
1165
|
+
}
|
|
1166
|
+
endHandover() {
|
|
1167
|
+
this.stopHandoverTimers();
|
|
1168
|
+
this.handover = null;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* The server's "this row was rewritten" notice (`lateDeliveryRepair`
|
|
1172
|
+
* `notifyViewersMessageRewritten`), delivered to every socket of the user
|
|
1173
|
+
* through the `user:<id>` room this socket joins on connect. A hint to read,
|
|
1174
|
+
* never a verdict.
|
|
1175
|
+
*/
|
|
1176
|
+
onConversationUpdate(data) {
|
|
1177
|
+
if (!this.handover || !data || data.messagesRepaired !== true)
|
|
1178
|
+
return;
|
|
1179
|
+
const opts = this.options.awaitHandover;
|
|
1180
|
+
const messageId = typeof data.messageId === 'string' ? data.messageId : undefined;
|
|
1181
|
+
const conversationId = typeof data.conversationId === 'string' ? data.conversationId : undefined;
|
|
1182
|
+
// Narrowest identity both sides know. When neither is known, read anyway:
|
|
1183
|
+
// a spare read is cheap and the record decides.
|
|
1184
|
+
let ours = true;
|
|
1185
|
+
if (messageId && opts?.responseMessageId)
|
|
1186
|
+
ours = messageId === opts.responseMessageId;
|
|
1187
|
+
else if (conversationId && opts?.conversationId)
|
|
1188
|
+
ours = conversationId === opts.conversationId;
|
|
1189
|
+
if (ours)
|
|
1190
|
+
this.requestHandoverRead();
|
|
1191
|
+
}
|
|
926
1192
|
debug(line) {
|
|
927
1193
|
try {
|
|
928
1194
|
(this.options.debug ?? defaultStreamDebug)(`session ${this.options.sessionId}: ${line}`);
|
|
@@ -1164,6 +1430,12 @@ class AgenticStreamClient {
|
|
|
1164
1430
|
// seqs and would read as a gap that never happened.
|
|
1165
1431
|
this.noteSeqGap(fresh);
|
|
1166
1432
|
this.noteDaemonToolEnvelopes(fresh);
|
|
1433
|
+
// The machine's own turn-end is what the server repairs or settles the
|
|
1434
|
+
// row on; seeing it relayed is the moment the record is about to move.
|
|
1435
|
+
if (this.handover &&
|
|
1436
|
+
fresh.some((env) => env?.ev?.t === 'turn-end')) {
|
|
1437
|
+
this.requestHandoverRead();
|
|
1438
|
+
}
|
|
1167
1439
|
const envelopeText = envelopesText(scoped);
|
|
1168
1440
|
if (envelopeText) {
|
|
1169
1441
|
this.accumulated += envelopeText;
|
|
@@ -1198,6 +1470,9 @@ class AgenticStreamClient {
|
|
|
1198
1470
|
socket.emit('session:join', this.sessionJoinPayload());
|
|
1199
1471
|
this.emit({ type: 'transport.connected', sessionId: this.options.sessionId });
|
|
1200
1472
|
void this.catchUpEnvelopes();
|
|
1473
|
+
// A repair notice sent while the transport was down is gone for good.
|
|
1474
|
+
if (this.handover)
|
|
1475
|
+
this.requestHandoverRead();
|
|
1201
1476
|
});
|
|
1202
1477
|
socket.on('disconnect', (reason) => {
|
|
1203
1478
|
this.transportDropped = true;
|
|
@@ -1294,22 +1569,42 @@ class AgenticStreamClient {
|
|
|
1294
1569
|
if (this.firstTimeSeen('text.completed', key))
|
|
1295
1570
|
this.emit({ ...event, type: 'text.completed' });
|
|
1296
1571
|
});
|
|
1572
|
+
// While a handed-over turn is waited on, a terminal frame — a repeat of the
|
|
1573
|
+
// hand-over failure through another room, or anything a later writer sends
|
|
1574
|
+
// — is a reason to read the record, not a verdict of its own (OSK-13543).
|
|
1297
1575
|
socket.on('execution:complete', (event) => {
|
|
1298
|
-
if (match(event))
|
|
1576
|
+
if (!match(event))
|
|
1577
|
+
return;
|
|
1578
|
+
if (this.handover)
|
|
1579
|
+
this.requestHandoverRead();
|
|
1580
|
+
else
|
|
1299
1581
|
this.finish('completed', event);
|
|
1300
1582
|
});
|
|
1301
1583
|
socket.on('execution:aborted', (event) => {
|
|
1302
|
-
if (match(event))
|
|
1584
|
+
if (!match(event))
|
|
1585
|
+
return;
|
|
1586
|
+
if (this.handover)
|
|
1587
|
+
this.requestHandoverRead();
|
|
1588
|
+
else
|
|
1303
1589
|
this.finish('aborted', event);
|
|
1304
1590
|
});
|
|
1305
1591
|
socket.on('execution:cancelled', (event) => {
|
|
1306
|
-
if (match(event))
|
|
1592
|
+
if (!match(event))
|
|
1593
|
+
return;
|
|
1594
|
+
if (this.handover)
|
|
1595
|
+
this.requestHandoverRead();
|
|
1596
|
+
else
|
|
1307
1597
|
this.finish('aborted', event);
|
|
1308
1598
|
});
|
|
1309
1599
|
socket.on('execution:error', (event) => {
|
|
1310
|
-
if (match(event))
|
|
1600
|
+
if (!match(event))
|
|
1601
|
+
return;
|
|
1602
|
+
if (this.handover)
|
|
1603
|
+
this.requestHandoverRead();
|
|
1604
|
+
else if (!(isDaemonHandoverFailure(event) && this.beginHandoverWait(event)))
|
|
1311
1605
|
this.finish('failed', event);
|
|
1312
1606
|
});
|
|
1607
|
+
socket.on('conversation:update', (data) => this.onConversationUpdate(data));
|
|
1313
1608
|
socket.on('permission:request', (event) => {
|
|
1314
1609
|
if (!match(event))
|
|
1315
1610
|
return;
|
|
@@ -1598,6 +1893,11 @@ class AgenticStreamClient {
|
|
|
1598
1893
|
// Any event is proof the stream is alive; the watchdog only fires on real
|
|
1599
1894
|
// silence.
|
|
1600
1895
|
this.armIdleProbe();
|
|
1896
|
+
// A handed-over turn can hold the terminal for minutes, so a person is told
|
|
1897
|
+
// why — in every human mode. `--no-stream` reserves stdout for the final
|
|
1898
|
+
// result, so there the line goes to stderr (OSK-13543).
|
|
1899
|
+
if (!this.options.jsonl && this.renderHandoverStatus(event))
|
|
1900
|
+
return;
|
|
1601
1901
|
// Accumulation and `onEvent` still run — only the incremental rendering is
|
|
1602
1902
|
// suppressed, so a quiet run is the same turn with a different display.
|
|
1603
1903
|
if (this.options.quiet)
|
|
@@ -1695,6 +1995,40 @@ class AgenticStreamClient {
|
|
|
1695
1995
|
this.renderedText = this.accumulated;
|
|
1696
1996
|
}
|
|
1697
1997
|
}
|
|
1998
|
+
/**
|
|
1999
|
+
* Human-mode lines for the hand-over wait. Returns true when the event is
|
|
2000
|
+
* fully rendered here; false lets the ordinary rendering continue.
|
|
2001
|
+
*/
|
|
2002
|
+
renderHandoverStatus(event) {
|
|
2003
|
+
const handover = event.handover;
|
|
2004
|
+
const isStatus = event.type === 'run.handed_over' ||
|
|
2005
|
+
(event.type === 'run.completed' && event.repaired === true) ||
|
|
2006
|
+
(event.type === 'run.failed' && !!handover);
|
|
2007
|
+
if (!isStatus)
|
|
2008
|
+
return false;
|
|
2009
|
+
const output = this.options.quiet
|
|
2010
|
+
? (this.options.awaitHandover?.statusOutput ?? process.stderr)
|
|
2011
|
+
: (this.options.output ?? process.stdout);
|
|
2012
|
+
if (event.type === 'run.handed_over') {
|
|
2013
|
+
output.write('\n[machine] A platform update restarted the server relaying this turn, but your ' +
|
|
2014
|
+
'machine is still working on it. Waiting up to ' +
|
|
2015
|
+
`${formatWaitDuration(Number(event.waitMs) || 0)} for its reply ` +
|
|
2016
|
+
'(Ctrl-C stops waiting; it does not stop your machine).\n');
|
|
2017
|
+
this.renderedText = this.accumulated;
|
|
2018
|
+
return true;
|
|
2019
|
+
}
|
|
2020
|
+
if (event.type === 'run.completed') {
|
|
2021
|
+
output.write('[machine] Your machine finished the turn, and its full reply was recovered.\n');
|
|
2022
|
+
return true;
|
|
2023
|
+
}
|
|
2024
|
+
if (handover?.outcome === 'expired') {
|
|
2025
|
+
output.write(`\n[machine] No reply from your machine within ${formatWaitDuration(Number(this.options.awaitHandover?.maxWaitMs) || 0)}. If it finishes later, the reply is saved to the conversation.\n`);
|
|
2026
|
+
}
|
|
2027
|
+
else {
|
|
2028
|
+
output.write('\n[machine] Your machine ended the turn without finishing it.\n');
|
|
2029
|
+
}
|
|
2030
|
+
return this.options.quiet === true;
|
|
2031
|
+
}
|
|
1698
2032
|
finish(status, event) {
|
|
1699
2033
|
if (this.settled || this.finishingReplay)
|
|
1700
2034
|
return;
|
|
@@ -1823,6 +2157,7 @@ class AgenticStreamClient {
|
|
|
1823
2157
|
responseMessageId: this.responseMessageId,
|
|
1824
2158
|
text: this.accumulated,
|
|
1825
2159
|
status,
|
|
2160
|
+
...(status === 'completed' && event.repaired === true ? { repaired: true } : {}),
|
|
1826
2161
|
lastEvent: event,
|
|
1827
2162
|
};
|
|
1828
2163
|
this.resolve(this.terminalResult);
|
|
@@ -136,6 +136,10 @@ export declare function writeToBackend(bundle: AuthBundle, options?: WriteOption
|
|
|
136
136
|
* auth-failure cleanup). Idempotent.
|
|
137
137
|
*/
|
|
138
138
|
export declare function clearAllBackends(): void;
|
|
139
|
+
export declare function takeResolvedKeychainRead(credential: {
|
|
140
|
+
token: string | null;
|
|
141
|
+
source: string;
|
|
142
|
+
}, expectedServerOrigin: string): ReadResult | null;
|
|
139
143
|
/**
|
|
140
144
|
* The low-level backends, exposed for the credential resolver's
|
|
141
145
|
* `readKeychainToken` / `readFileToken` slots. Internal callers should
|
package/dist/lib/auth-storage.js
CHANGED
|
@@ -41,6 +41,7 @@ exports.readFromBackend = readFromBackend;
|
|
|
41
41
|
exports.describeForeignCredential = describeForeignCredential;
|
|
42
42
|
exports.writeToBackend = writeToBackend;
|
|
43
43
|
exports.clearAllBackends = clearAllBackends;
|
|
44
|
+
exports.takeResolvedKeychainRead = takeResolvedKeychainRead;
|
|
44
45
|
exports.normalizeServerOrigin = normalizeServerOrigin;
|
|
45
46
|
exports.__authFilePathForTest = __authFilePathForTest;
|
|
46
47
|
/**
|
|
@@ -504,6 +505,11 @@ function unlinkQuietly(p) {
|
|
|
504
505
|
function readFromKeychain() {
|
|
505
506
|
if (!keychain.isAvailable())
|
|
506
507
|
return null;
|
|
508
|
+
// One pass: the owner record is read once for the whole bundle, not once per
|
|
509
|
+
// secret plus once for itself (see `withKeychainReadPass`).
|
|
510
|
+
return keychain.withKeychainReadPass(readFromKeychainInPass);
|
|
511
|
+
}
|
|
512
|
+
function readFromKeychainInPass() {
|
|
507
513
|
const tokenEntry = keychain.readToken();
|
|
508
514
|
if (!tokenEntry) {
|
|
509
515
|
// Read came back empty despite "available" — don't cache a false
|
|
@@ -769,6 +775,7 @@ function describeForeignCredential(result) {
|
|
|
769
775
|
function writeToBackend(bundle, options = {}) {
|
|
770
776
|
if ((0, harness_context_1.isHarnessCliContext)())
|
|
771
777
|
throw new harness_context_1.HarnessCredentialPersistRefused();
|
|
778
|
+
lastKeychainTokenRead = null;
|
|
772
779
|
const requestedOrigin = options.serverOrigin ?? bundle.serverOrigin;
|
|
773
780
|
let persistedBundle = bundle;
|
|
774
781
|
if (requestedOrigin !== undefined) {
|
|
@@ -821,9 +828,34 @@ function writeToBackend(bundle, options = {}) {
|
|
|
821
828
|
* auth-failure cleanup). Idempotent.
|
|
822
829
|
*/
|
|
823
830
|
function clearAllBackends() {
|
|
831
|
+
lastKeychainTokenRead = null;
|
|
824
832
|
clearKeychainBackend();
|
|
825
833
|
deleteFileBackend();
|
|
826
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* The Keychain read the credential resolver just made, for the ONE caller that
|
|
837
|
+
* reads the same bundle again a moment later: `BaseCommand.init()` resolves the
|
|
838
|
+
* credential and then asks `ensureFreshCliCredential` whether it needs a
|
|
839
|
+
* refresh, which re-read every slot (another six `security` spawns, ~100ms) to
|
|
840
|
+
* learn the expiry the first read already had.
|
|
841
|
+
*
|
|
842
|
+
* Handed out at most once, only for the credential that read produced, only
|
|
843
|
+
* for the same server origin, and dropped by any write or clear in this
|
|
844
|
+
* process. It never stands in for the re-read under the auth lock: a refresh
|
|
845
|
+
* still reads the store fresh before it spends a refresh token.
|
|
846
|
+
*/
|
|
847
|
+
let lastKeychainTokenRead = null;
|
|
848
|
+
function takeResolvedKeychainRead(credential, expectedServerOrigin) {
|
|
849
|
+
const read = lastKeychainTokenRead;
|
|
850
|
+
lastKeychainTokenRead = null;
|
|
851
|
+
if (!read?.bundle || credential.source !== 'keychain')
|
|
852
|
+
return null;
|
|
853
|
+
if (!credential.token || read.bundle.token !== credential.token)
|
|
854
|
+
return null;
|
|
855
|
+
if (read.expectedServerOrigin !== normalizeServerOrigin(expectedServerOrigin))
|
|
856
|
+
return null;
|
|
857
|
+
return read;
|
|
858
|
+
}
|
|
827
859
|
/**
|
|
828
860
|
* The low-level backends, exposed for the credential resolver's
|
|
829
861
|
* `readKeychainToken` / `readFileToken` slots. Internal callers should
|
|
@@ -847,6 +879,7 @@ exports.backends = {
|
|
|
847
879
|
readKeychainToken(expectedServerOrigin) {
|
|
848
880
|
const read = readKeychainAssessed();
|
|
849
881
|
const result = assessBundleOrigin(read.bundle, 'keychain', expectedServerOrigin);
|
|
882
|
+
lastKeychainTokenRead = result.bundle && result.originStatus === 'matched' ? result : null;
|
|
850
883
|
return result.bundle?.token ?? null;
|
|
851
884
|
},
|
|
852
885
|
readFileToken(expectedServerOrigin) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** One window's counts, for all daemons and for field daemons (not Dedicated Runtime guests). */
|
|
2
|
+
export interface ComputerAdoptionCounts {
|
|
3
|
+
active: number;
|
|
4
|
+
computerOnly: number;
|
|
5
|
+
both: number;
|
|
6
|
+
legacyOnly: number;
|
|
7
|
+
neither: number;
|
|
8
|
+
legacyOnlyShare: number | null;
|
|
9
|
+
announcingOldShare: number | null;
|
|
10
|
+
}
|
|
11
|
+
/** One client build that opened a `browser:liveview:*` view inside the window (OSK-13616). */
|
|
12
|
+
export interface ComputerLiveViewClient {
|
|
13
|
+
platform: string;
|
|
14
|
+
os: string;
|
|
15
|
+
version: string;
|
|
16
|
+
lastSeenAt: string;
|
|
17
|
+
}
|
|
18
|
+
/** The client half of the retirement criterion, from socket client identity. */
|
|
19
|
+
export interface ComputerLiveViewClients {
|
|
20
|
+
readable: boolean;
|
|
21
|
+
windowDays: number;
|
|
22
|
+
/** When the record began; the half is only measurable once it covers a whole window. */
|
|
23
|
+
recordingSince?: string;
|
|
24
|
+
coversWindow?: boolean;
|
|
25
|
+
clients: ComputerLiveViewClient[];
|
|
26
|
+
mobileVersions?: string[];
|
|
27
|
+
/** Clients that sent no identity: possibly an old mobile build. */
|
|
28
|
+
unattributed?: number;
|
|
29
|
+
mobileHalfMet: boolean | null;
|
|
30
|
+
}
|
|
31
|
+
/** `GET /api/machines/computer-adoption` — admin only (OSK-13578, OSK-13616). */
|
|
32
|
+
export interface ComputerAdoption {
|
|
33
|
+
generatedAt: string;
|
|
34
|
+
windows: Record<string, {
|
|
35
|
+
all: ComputerAdoptionCounts;
|
|
36
|
+
field: ComputerAdoptionCounts;
|
|
37
|
+
}>;
|
|
38
|
+
announced: Record<string, number>;
|
|
39
|
+
/** Absent from a server older than OSK-13616. */
|
|
40
|
+
liveViewClients?: ComputerLiveViewClients;
|
|
41
|
+
criterion: {
|
|
42
|
+
maxLegacyOnlyShare: number;
|
|
43
|
+
windowDays: number;
|
|
44
|
+
mobileMoved: string;
|
|
45
|
+
oldFamilies: string[];
|
|
46
|
+
daemonHalfMet: boolean | null;
|
|
47
|
+
mobileHalfMet?: boolean | null;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* How many daemons seen recently are still on the old event families rather
|
|
52
|
+
* than the computer protocol — the number the families' retirement waits on.
|
|
53
|
+
*/
|
|
54
|
+
export declare function getComputerAdoption(): Promise<ComputerAdoption>;
|
|
55
|
+
export declare function computerAdoptionLines(read: ComputerAdoption): string[];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getComputerAdoption = getComputerAdoption;
|
|
4
|
+
exports.computerAdoptionLines = computerAdoptionLines;
|
|
5
|
+
const api_fetch_1 = require("./api-fetch");
|
|
6
|
+
/**
|
|
7
|
+
* How many daemons seen recently are still on the old event families rather
|
|
8
|
+
* than the computer protocol — the number the families' retirement waits on.
|
|
9
|
+
*/
|
|
10
|
+
async function getComputerAdoption() {
|
|
11
|
+
return (0, api_fetch_1.apiFetch)('/api/machines/computer-adoption');
|
|
12
|
+
}
|
|
13
|
+
function percent(share) {
|
|
14
|
+
return share === null ? 'n/a (no active daemons)' : `${(share * 100).toFixed(1)}%`;
|
|
15
|
+
}
|
|
16
|
+
function countsLines(label, counts) {
|
|
17
|
+
return [
|
|
18
|
+
` ${label}: ${counts.active} active`,
|
|
19
|
+
` computer only ${counts.computerOnly} · both ${counts.both} · old families only ${counts.legacyOnly} · neither ${counts.neither}`,
|
|
20
|
+
` still on the old families only: ${percent(counts.legacyOnlyShare)} announcing an old family: ${percent(counts.announcingOldShare)}`,
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
function mobileHalfLine(read) {
|
|
24
|
+
const clients = read.liveViewClients;
|
|
25
|
+
if (!clients)
|
|
26
|
+
return ` mobile half: ${read.criterion.mobileMoved}`;
|
|
27
|
+
if (!clients.readable)
|
|
28
|
+
return ' mobile half: not measurable: the client record could not be read';
|
|
29
|
+
const versions = clients.mobileVersions ?? [];
|
|
30
|
+
if (clients.mobileHalfMet === false) {
|
|
31
|
+
return ` mobile half (no mobile build opened a live view in ${clients.windowDays} days): NOT met — still on it: ${versions.join(', ')}`;
|
|
32
|
+
}
|
|
33
|
+
if (clients.mobileHalfMet === null && clients.coversWindow === false) {
|
|
34
|
+
return ` mobile half: not measurable yet: the record began ${clients.recordingSince ?? 'recently'} and needs ${clients.windowDays} days`;
|
|
35
|
+
}
|
|
36
|
+
if (clients.mobileHalfMet === null) {
|
|
37
|
+
return ` mobile half: not measurable: ${clients.unattributed ?? 0} client build(s) opened a live view without saying what they are`;
|
|
38
|
+
}
|
|
39
|
+
return ` mobile half (no mobile build opened a live view in ${clients.windowDays} days): MET`;
|
|
40
|
+
}
|
|
41
|
+
function clientLines(read) {
|
|
42
|
+
const clients = read.liveViewClients;
|
|
43
|
+
if (!clients?.readable || clients.clients.length === 0)
|
|
44
|
+
return [];
|
|
45
|
+
return [
|
|
46
|
+
'',
|
|
47
|
+
`Client builds that opened a browser:liveview:* view in the last ${clients.windowDays}d`,
|
|
48
|
+
...clients.clients.map((client) => ` ${client.platform} ${client.os} ${client.version} last ${client.lastSeenAt}`),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
function computerAdoptionLines(read) {
|
|
52
|
+
const lines = [`Computer protocol adoption (${read.generatedAt})`];
|
|
53
|
+
for (const [window, scopes] of Object.entries(read.windows)) {
|
|
54
|
+
lines.push('', `Seen in the last ${window}`);
|
|
55
|
+
lines.push(...countsLines('all daemons', scopes.all));
|
|
56
|
+
lines.push(...countsLines('field daemons (no guests)', scopes.field));
|
|
57
|
+
}
|
|
58
|
+
const { criterion } = read;
|
|
59
|
+
let verdict = 'NOT met';
|
|
60
|
+
if (criterion.daemonHalfMet === null)
|
|
61
|
+
verdict = 'not measurable: no active daemons';
|
|
62
|
+
else if (criterion.daemonHalfMet)
|
|
63
|
+
verdict = 'MET';
|
|
64
|
+
lines.push('', `Retirement of ${criterion.oldFamilies.join(', ')}`, ` daemon half (< ${(criterion.maxLegacyOnlyShare * 100).toFixed(0)}% of daemons active in ${criterion.windowDays} days on the old families only): ${verdict}`, mobileHalfLine(read));
|
|
65
|
+
lines.push(...clientLines(read));
|
|
66
|
+
return lines;
|
|
67
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* The bridge for `skrr computer` — the machine-local consent for the
|
|
4
|
+
* computer's new surfaces on a personal machine (design §18, D11;
|
|
5
|
+
* OSK-13532): the file surface and a person's own browser windows.
|
|
6
|
+
*
|
|
7
|
+
* The consent is a decision made ON the machine, so it is implemented by the
|
|
8
|
+
* local daemon (`skrrd computer allow|deny|status`) writing daemon.json —
|
|
9
|
+
* never by a REST route, because nothing sent from the cloud may grant it.
|
|
10
|
+
* Keeping the CLI a thin forwarder preserves that boundary: `skrr` carries
|
|
11
|
+
* the verb to the same binary `skrrd` runs it on.
|
|
12
|
+
*/
|
|
13
|
+
export declare function forwardComputerConsent(command: Command, passthrough: readonly string[]): Promise<void>;
|