@skrr-ai/cli 0.1.124 → 0.1.126

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.
@@ -25,6 +25,28 @@ export declare const DETACH_EXIT_CODE = 130;
25
25
  * answers true.
26
26
  */
27
27
  export declare const STREAM_LOST_EXIT_CODE = 75;
28
+ /**
29
+ * The signals that stop a running chat.
30
+ *
31
+ * Ctrl-C was the only one handled (OSK-2938). SIGTERM (a CI cancel, `timeout`, a process manager) and
32
+ * SIGHUP (the terminal closing) killed the CLI in under 100 ms with no cancel sent, so the agent kept
33
+ * running tools on the person's machine with nobody watching and held the folder, and the next message
34
+ * was refused as `managed_task_workspace_busy` (OSK-20677). `machines dedicated cp` already treats all
35
+ * three as cancel and exits as a shell would; this does the same.
36
+ */
37
+ export declare const INTERRUPT_SIGNALS: readonly ["SIGINT", "SIGTERM", "SIGHUP"];
38
+ export type InterruptSignal = (typeof INTERRUPT_SIGNALS)[number];
39
+ /** 128 plus the signal's number, as a shell reports a process the signal killed: 130, 143, 129. */
40
+ export declare function interruptExitCode(signal: InterruptSignal | null): number;
41
+ /**
42
+ * The cancel notice, in words that fit the signal. "Press Ctrl-C again" is wrong advice after a
43
+ * SIGTERM, and after SIGHUP nobody is looking: the terminal that would show it is gone.
44
+ */
45
+ export declare function cancelNotice(input: {
46
+ signal: InterruptSignal;
47
+ sessionId: string;
48
+ graceSeconds: number;
49
+ }): string | null;
28
50
  /**
29
51
  * Exit code for a streamed run that reached its terminal state: 1 when it
30
52
  * failed, 0 otherwise.
@@ -59,6 +81,31 @@ export declare function interruptedDetachMessage(input: {
59
81
  /** The second Ctrl-C: the CLI stopped waiting rather than running out the grace period. */
60
82
  forced?: boolean;
61
83
  }): string;
84
+ /**
85
+ * `--jsonl` prints `text.delta` as a real delta unless `--cumulative` asks for the whole answer
86
+ * so far in every event. The cumulative form costs the square of the answer's length (a 57 KB
87
+ * answer printed 34.6 MB, OSK-20596), which is the wrong default for the mode scripts and other
88
+ * agents drive. `--incremental` is the old opt-in for what is now the default and changes nothing.
89
+ */
90
+ export declare function jsonlTextForm(flags: {
91
+ cumulative?: boolean;
92
+ }): {
93
+ incrementalText: boolean;
94
+ };
95
+ /**
96
+ * What to say when the user pressed Ctrl-C and the server then confirmed the run ended.
97
+ *
98
+ * The warning printed at the keypress ("a tool call already running on the runtime is not
99
+ * guaranteed to stop") is written before the outcome is known. When the run settles as `aborted`
100
+ * it is no longer the whole truth, and left as the last line it reads as a cancel that failed:
101
+ * the CLI exited without a word, the prompt came back, and a person could not tell whether to go
102
+ * and kill something (OSK-20603). A script has `run.result {status: "aborted"}`; a terminal gets
103
+ * this line. It claims what the server said, that the run ended, and nothing about processes.
104
+ */
105
+ export declare function cancelledRunMessage(input: {
106
+ sessionId: string;
107
+ conversationId?: string | null;
108
+ }): string;
62
109
  /**
63
110
  * What the durable record says about a turn, read from its messages.
64
111
  *
@@ -228,6 +275,7 @@ export default class AgentsChat extends BaseCommand {
228
275
  'thread-root': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
229
276
  'no-stream': import("@oclif/core/interfaces").BooleanFlag<boolean>;
230
277
  quick: import("@oclif/core/interfaces").BooleanFlag<boolean>;
278
+ cumulative: import("@oclif/core/interfaces").BooleanFlag<boolean>;
231
279
  incremental: import("@oclif/core/interfaces").BooleanFlag<boolean>;
232
280
  jsonl: import("@oclif/core/interfaces").BooleanFlag<boolean>;
233
281
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MAX_HANDOVER_WAIT_SECONDS = exports.DEFAULT_HANDOVER_WAIT_SECONDS = exports.STREAM_LOST_EXIT_CODE = exports.DETACH_EXIT_CODE = void 0;
3
+ exports.MAX_HANDOVER_WAIT_SECONDS = exports.DEFAULT_HANDOVER_WAIT_SECONDS = exports.INTERRUPT_SIGNALS = exports.STREAM_LOST_EXIT_CODE = exports.DETACH_EXIT_CODE = void 0;
4
+ exports.interruptExitCode = interruptExitCode;
5
+ exports.cancelNotice = cancelNotice;
4
6
  exports.streamedResultExitCode = streamedResultExitCode;
5
7
  exports.interruptedDetachMessage = interruptedDetachMessage;
8
+ exports.jsonlTextForm = jsonlTextForm;
9
+ exports.cancelledRunMessage = cancelledRunMessage;
6
10
  exports.reconcileTerminalFromMessages = reconcileTerminalFromMessages;
7
11
  exports.handoverStateFromMessages = handoverStateFromMessages;
8
12
  exports.handoverInterruptMessage = handoverInterruptMessage;
@@ -47,6 +51,35 @@ exports.DETACH_EXIT_CODE = 130;
47
51
  * answers true.
48
52
  */
49
53
  exports.STREAM_LOST_EXIT_CODE = 75;
54
+ /**
55
+ * The signals that stop a running chat.
56
+ *
57
+ * Ctrl-C was the only one handled (OSK-2938). SIGTERM (a CI cancel, `timeout`, a process manager) and
58
+ * SIGHUP (the terminal closing) killed the CLI in under 100 ms with no cancel sent, so the agent kept
59
+ * running tools on the person's machine with nobody watching and held the folder, and the next message
60
+ * was refused as `managed_task_workspace_busy` (OSK-20677). `machines dedicated cp` already treats all
61
+ * three as cancel and exits as a shell would; this does the same.
62
+ */
63
+ exports.INTERRUPT_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
64
+ /** 128 plus the signal's number, as a shell reports a process the signal killed: 130, 143, 129. */
65
+ function interruptExitCode(signal) {
66
+ if (signal === 'SIGTERM')
67
+ return 143;
68
+ if (signal === 'SIGHUP')
69
+ return 129;
70
+ return exports.DETACH_EXIT_CODE;
71
+ }
72
+ /**
73
+ * The cancel notice, in words that fit the signal. "Press Ctrl-C again" is wrong advice after a
74
+ * SIGTERM, and after SIGHUP nobody is looking: the terminal that would show it is gone.
75
+ */
76
+ function cancelNotice(input) {
77
+ if (input.signal === 'SIGHUP')
78
+ return null;
79
+ const again = input.signal === 'SIGINT' ? 'press Ctrl-C again' : `send ${input.signal} again`;
80
+ return (`\nCancelling run ${input.sessionId}… (waiting up to ${input.graceSeconds}s; ${again} to detach). ` +
81
+ 'A tool call already running on the runtime is not guaranteed to stop.');
82
+ }
50
83
  /**
51
84
  * Exit code for a streamed run that reached its terminal state: 1 when it
52
85
  * failed, 0 otherwise.
@@ -88,6 +121,29 @@ function interruptedDetachMessage(input) {
88
121
  ? ''
89
122
  : ', and look for a surviving process with `ps -ef | grep -i claude`'));
90
123
  }
124
+ /**
125
+ * `--jsonl` prints `text.delta` as a real delta unless `--cumulative` asks for the whole answer
126
+ * so far in every event. The cumulative form costs the square of the answer's length (a 57 KB
127
+ * answer printed 34.6 MB, OSK-20596), which is the wrong default for the mode scripts and other
128
+ * agents drive. `--incremental` is the old opt-in for what is now the default and changes nothing.
129
+ */
130
+ function jsonlTextForm(flags) {
131
+ return { incrementalText: flags.cumulative !== true };
132
+ }
133
+ /**
134
+ * What to say when the user pressed Ctrl-C and the server then confirmed the run ended.
135
+ *
136
+ * The warning printed at the keypress ("a tool call already running on the runtime is not
137
+ * guaranteed to stop") is written before the outcome is known. When the run settles as `aborted`
138
+ * it is no longer the whole truth, and left as the last line it reads as a cancel that failed:
139
+ * the CLI exited without a word, the prompt came back, and a person could not tell whether to go
140
+ * and kill something (OSK-20603). A script has `run.result {status: "aborted"}`; a terminal gets
141
+ * this line. It claims what the server said, that the run ended, and nothing about processes.
142
+ */
143
+ function cancelledRunMessage(input) {
144
+ return (`Run ${input.sessionId} cancelled: the server confirmed it ended.` +
145
+ (input.conversationId ? ` Continue the conversation with --conversation ${input.conversationId}.` : ''));
146
+ }
91
147
  /**
92
148
  * What the durable record says about a turn, read from its messages.
93
149
  *
@@ -379,20 +435,29 @@ class AgentsChat extends base_command_1.BaseCommand {
379
435
  'agent’s runtime, tools, session, or working directory and cannot perform work — ' +
380
436
  'it is for inline text assistance. Use --no-stream for a real turn.',
381
437
  }),
438
+ cumulative: core_1.Flags.boolean({
439
+ description: 'With --jsonl, make `text.delta` carry the whole answer so far in `text` (plus `delta`, ' +
440
+ 'the characters it adds, when it only appends) and repeat the envelopes that ' +
441
+ '`session.output` already carries: the form --jsonl had before it was incremental by ' +
442
+ 'default. A long answer written this way grows with the square of its length ' +
443
+ '(a 57 KB answer printed 34.6 MB), so use it only for a consumer that must read ' +
444
+ 'the answer from any single event.',
445
+ dependsOn: ['jsonl'],
446
+ exclusive: ['incremental'],
447
+ }),
382
448
  incremental: core_1.Flags.boolean({
383
- description: 'With --jsonl, make `text.delta` a real delta: it carries only `delta`, the characters ' +
384
- 'it adds, instead of the whole answer so far, and leaves out the envelopes that ' +
385
- '`session.output` already carries. A rewrite that is not an append arrives as ' +
386
- '`replace`, the new full text. `run.result` still holds the whole answer. Without it ' +
387
- 'a long answer written as JSON lines grows with the square of its length.',
449
+ hidden: true,
450
+ description: 'No longer needed: --jsonl is incremental by default. Accepted so scripts that already ' +
451
+ 'pass it keep working.',
388
452
  dependsOn: ['jsonl'],
389
453
  }),
390
454
  jsonl: core_1.Flags.boolean({
391
455
  description: 'Emit one JSON event per line while streaming. Every line is a complete JSON value. ' +
392
- 'Assistant text arrives as `text.delta` whose `text` is the CUMULATIVE answer so far, ' +
393
- 'not a delta — take the last one, or `run.result` at the end. Each `text.delta` also ' +
394
- 'carries `delta`, the characters it adds, when it only appends to the one before; ' +
395
- 'with --incremental it carries nothing else of the text (see that flag). The same text is ' +
456
+ 'Assistant text arrives as `text.delta`, a real delta: `delta` is the characters it ' +
457
+ 'adds, and a rewrite that is not an append arrives as `replace`, the new full text. ' +
458
+ 'Append the `delta`s, or take `run.result` at the end, which always holds the whole ' +
459
+ 'answer. (--cumulative restores the older form, where `text` is the whole answer so ' +
460
+ 'far; it grows with the square of the answer.) The same text is ' +
396
461
  'mirrored inside `session.output` envelopes for consumers that want the raw stream, ' +
397
462
  'so folding both doubles the answer: pick ONE lane. A widget the agent wrote ' +
398
463
  '(a block of CSS, HTML and JavaScript the apps draw) is its text description in ' +
@@ -813,7 +878,7 @@ class AgentsChat extends base_command_1.BaseCommand {
813
878
  sessionId: started.sessionId,
814
879
  conversationId,
815
880
  jsonl: flags.jsonl,
816
- incrementalText: flags.incremental,
881
+ ...jsonlTextForm(flags),
817
882
  quiet,
818
883
  approveTools: flags['approve-tools'],
819
884
  reconcileTerminal,
@@ -848,10 +913,22 @@ class AgentsChat extends base_command_1.BaseCommand {
848
913
  detach = () => resolve('detached');
849
914
  });
850
915
  let interrupts = 0;
916
+ // The signal that started the stop. Its exit status is what a script reads (130, 143 or 129).
917
+ let interruptedBy = null;
918
+ // After SIGHUP the terminal is gone and a write to it throws; the notice is lost, the stop is not.
919
+ const writeNotice = (fd, text) => {
920
+ try {
921
+ (0, node_fs_1.writeSync)(fd, text);
922
+ }
923
+ catch (_error) {
924
+ /* the terminal that would show this is closed */
925
+ }
926
+ };
851
927
  let forcedDetachAnnounced = false;
852
928
  let graceTimer = null;
853
- const onInterrupt = () => {
929
+ const onInterrupt = (signal = 'SIGINT') => {
854
930
  interrupts += 1;
931
+ interruptedBy ??= signal;
855
932
  const action = interruptAction({ interrupts, awaitingHandover: client.awaitingHandover() });
856
933
  if (action === 'stop-handover-wait') {
857
934
  // Nothing to cancel and nothing to grace-wait for: stop at once.
@@ -862,7 +939,7 @@ class AgentsChat extends base_command_1.BaseCommand {
862
939
  conversationId: started.conversationId,
863
940
  });
864
941
  if (flags.jsonl) {
865
- (0, node_fs_1.writeSync)(process.stdout.fd, `${JSON.stringify({
942
+ writeNotice(process.stdout.fd, `${JSON.stringify({
866
943
  type: 'run.detached',
867
944
  sessionId: started.sessionId,
868
945
  conversationId: started.conversationId,
@@ -872,9 +949,9 @@ class AgentsChat extends base_command_1.BaseCommand {
872
949
  })}\n`);
873
950
  }
874
951
  else {
875
- (0, node_fs_1.writeSync)(process.stderr.fd, `\n${message}\n`);
952
+ writeNotice(process.stderr.fd, `\n${message}\n`);
876
953
  }
877
- process.exitCode = exports.DETACH_EXIT_CODE;
954
+ process.exitCode = interruptExitCode(interruptedBy);
878
955
  }
879
956
  client.close();
880
957
  detach?.();
@@ -883,9 +960,13 @@ class AgentsChat extends base_command_1.BaseCommand {
883
960
  if (action === 'cancel') {
884
961
  client.cancel();
885
962
  if (!flags.jsonl) {
886
- this.log(`\nCancelling run ${started.sessionId}… (waiting up to ${CANCEL_GRACE_MS / 1000}s; ` +
887
- 'press Ctrl-C again to detach). A tool call already running on the runtime is ' +
888
- 'not guaranteed to stop.');
963
+ const notice = cancelNotice({
964
+ signal,
965
+ sessionId: started.sessionId,
966
+ graceSeconds: CANCEL_GRACE_MS / 1000,
967
+ });
968
+ if (notice)
969
+ writeNotice(process.stdout.fd, `${notice}\n`);
889
970
  }
890
971
  graceTimer = setTimeout(() => detach?.(), CANCEL_GRACE_MS);
891
972
  graceTimer.unref?.();
@@ -899,7 +980,7 @@ class AgentsChat extends base_command_1.BaseCommand {
899
980
  forced: true,
900
981
  });
901
982
  if (flags.jsonl) {
902
- (0, node_fs_1.writeSync)(process.stdout.fd, `${JSON.stringify({
983
+ writeNotice(process.stdout.fd, `${JSON.stringify({
903
984
  type: 'run.detached',
904
985
  sessionId: started.sessionId,
905
986
  conversationId: started.conversationId,
@@ -908,13 +989,14 @@ class AgentsChat extends base_command_1.BaseCommand {
908
989
  })}\n`);
909
990
  }
910
991
  else {
911
- (0, node_fs_1.writeSync)(process.stderr.fd, `\n${message}\n`);
992
+ writeNotice(process.stderr.fd, `\n${message}\n`);
912
993
  }
913
- process.exitCode = exports.DETACH_EXIT_CODE;
994
+ process.exitCode = interruptExitCode(interruptedBy);
914
995
  }
915
996
  detach?.();
916
997
  };
917
- process.on('SIGINT', onInterrupt);
998
+ for (const name of exports.INTERRUPT_SIGNALS)
999
+ process.on(name, onInterrupt);
918
1000
  try {
919
1001
  // OSK-2944 — the run was ACCEPTED before we ever tried to attach. If the
920
1002
  // stream cannot be attached, that is a lost view of a live run, not a
@@ -954,7 +1036,7 @@ class AgentsChat extends base_command_1.BaseCommand {
954
1036
  // 130 only when a signal actually caused this; otherwise the stream was
955
1037
  // lost and claiming SIGINT lies to every script reading the code
956
1038
  // (OSK-7660).
957
- const exit = interrupts > 0 ? exports.DETACH_EXIT_CODE : exports.STREAM_LOST_EXIT_CODE;
1039
+ const exit = interrupts > 0 ? interruptExitCode(interruptedBy) : exports.STREAM_LOST_EXIT_CODE;
958
1040
  if (flags.jsonl) {
959
1041
  (0, agentic_stream_1.writeJsonLine)(process.stdout, {
960
1042
  type: 'run.detached',
@@ -1005,11 +1087,23 @@ class AgentsChat extends base_command_1.BaseCommand {
1005
1087
  this.error(noOutput, { exit: 1 });
1006
1088
  this.log(result.text);
1007
1089
  }
1090
+ else if (result.status === 'aborted' && interruptedBy !== 'SIGHUP') {
1091
+ // Streaming human mode: say the cancel took, rather than leave the keypress warning last.
1092
+ // (Not after SIGHUP: the terminal that would show it is gone.)
1093
+ this.log(`\n${cancelledRunMessage({ sessionId: started.sessionId, conversationId: started.conversationId })}`);
1094
+ }
1095
+ // A run stopped by SIGTERM or SIGHUP ends the way a shell reports a process the signal killed,
1096
+ // so a script can tell a cancelled run from one that finished. Ctrl-C keeps its existing status
1097
+ // (a cancel that took is a success, a detach is 130).
1098
+ if (interruptedBy === 'SIGTERM' || interruptedBy === 'SIGHUP') {
1099
+ process.exitCode = interruptExitCode(interruptedBy);
1100
+ }
1008
1101
  }
1009
1102
  finally {
1010
1103
  if (graceTimer)
1011
1104
  clearTimeout(graceTimer);
1012
- process.removeListener('SIGINT', onInterrupt);
1105
+ for (const name of exports.INTERRUPT_SIGNALS)
1106
+ process.removeListener(name, onInterrupt);
1013
1107
  client.close();
1014
1108
  }
1015
1109
  }
@@ -350,7 +350,7 @@
350
350
  "id": "memory",
351
351
  "title": "Where an Agent's knowledge lives",
352
352
  "readWhen": "you need to read or change what an Agent knows, or tell its Memory from its configuration",
353
- "tokens": 858,
353
+ "tokens": 891,
354
354
  "aliases": [],
355
355
  "explains": [
356
356
  "MEMORY_NOT_MOUNTED",
@@ -489,7 +489,7 @@
489
489
  "version": 4,
490
490
  "routed": true,
491
491
  "aliases": [],
492
- "tokens": 861,
492
+ "tokens": 865,
493
493
  "sections": [
494
494
  {
495
495
  "id": "model",
@@ -709,7 +709,7 @@
709
709
  ]
710
710
  }
711
711
  ],
712
- "hash": "da48eab97c0c8b0102894a1256266cd67df3611313caec27ffff1ef2c2ec87ae",
712
+ "hash": "3581ad4744d3aae5df82a9358ddb6f87e823b4928104006581e16b3a4d821b2e",
713
713
  "tokens": 544
714
714
  },
715
715
  "routedIndex": {
@@ -1063,7 +1063,7 @@
1063
1063
  "id": "memory",
1064
1064
  "title": "Where an Agent's knowledge lives",
1065
1065
  "readWhen": "you need to read or change what an Agent knows, or tell its Memory from its configuration",
1066
- "tokens": 858,
1066
+ "tokens": 891,
1067
1067
  "aliases": [],
1068
1068
  "explains": [
1069
1069
  "MEMORY_NOT_MOUNTED",
@@ -1202,7 +1202,7 @@
1202
1202
  "version": 4,
1203
1203
  "routed": true,
1204
1204
  "aliases": [],
1205
- "tokens": 861,
1205
+ "tokens": 865,
1206
1206
  "sections": [
1207
1207
  {
1208
1208
  "id": "model",
@@ -1353,7 +1353,7 @@
1353
1353
  ]
1354
1354
  }
1355
1355
  ],
1356
- "hash": "ac18c43c11f5a755bd8f80f49323edc91d9ee75d3448a18e5ffc7ba30a484bbc",
1356
+ "hash": "2f65b076744f03768c49bce61d58f4ee26354c007316777602a3567f3d71f35d",
1357
1357
  "tokens": 493
1358
1358
  },
1359
1359
  "cards": {
@@ -1676,7 +1676,7 @@
1676
1676
  "routed": true,
1677
1677
  "aliases": [],
1678
1678
  "body": "## Concept model\n\n| Noun | Question it answers |\n| --- | --- |\n| **Agent** | Who acts, and with which identity, instructions, model, tools and tone? |\n| **Harness** | Which provider runs its turns, on which machine? One machine × one provider: Claude Code and Codex on one laptop are two Harnesses. |\n| **Machine** | Which computer: a personal machine running the daemon, a Dedicated Runtime, or a Hosted Machine? |\n| **Release** | Which exact, installable snapshot of the Agent's portable specification? |\n| **Role** | Which portable responsibility (purpose, scope, parameters) can be installed on an Agent? |\n| **Memory Store** | What does the Agent know, kept for the Agent or for a Space? |\n| **Notepad** | What does one person privately want this Agent to keep in mind? |\n\n## Decide\n\n- **Who am I?** The managed identity block in your instruction file names the Agent this machine\n speaks as. `--agent <agent-id>` chooses which Agent a message speaks as. Neither sets authorship:\n the server records the author of every write from the authenticated runtime, and `skrr context`\n reports only whether this runtime carries an Agent binding.\n- How it writes (shorter, plainer, warmer) is **tone**. What it does or must never do is an\n **instruction block**. Neither belongs in a Memory Store or the Notepad.\n- A preference the person tells you to remember is a Memory proposal they review, not an edit to\n your own instructions. Work to do later, or to keep checking, belongs to a commitment.\n- Where it runs is its execution targets. A Machine is not a Harness: pin the Harness.\n\n## Workflow\n\n### Change an Agent's configuration\n\n1. Read the current value of exactly what you will change, on the Agent the owner named.\n2. Make the narrowest change asked for: add or edit one instruction block rather than replacing\n all of them.\n3. Read the resolved state back and report that. A workspace policy can override a tone you set.\n\nThe commands for each step are in the `configure` section; `--help` gives their flags.\n\n## Authority\n\n| Actor | May do | Only a person may do |\n| --- | --- | --- |\n| Agent | Read any Agent it can view; when the owner asks, change an Agent's instructions, tone, model or tools, create an Agent, or delete one the owner's own message names; propose a Memory change | Widen an Agent's permissions: permission mode, network egress, tool sign-in; publish, list, price or share an Agent; connect a channel; choose paid compute; approve a Memory promotion; set Memory governance |\n| Person | Everything in both columns | — |\n\nAn Agent never widens its own permissions or membership, even where a command would accept it.\n\n## Live state\n\n- `skrr agents show <agent-id> --expanded` — the full configuration, execution targets included.\n- `skrr agents instructions show <agent-id>` — the instruction blocks in order, with instance ids.\n- `skrr agents tone show <agent-id>` — the tone it actually writes in, and where that came from.\n- `skrr harnesses list` and `skrr machines list` — Harness ids and the Machines they run on.\n- `skrr memory mounts resolve --agent <agent-id>` — the Memory Stores it can read or write.\n\n## Related guides\n\n- `commitments` — what an Agent is responsible for over time, and making something happen later.\n- `learning` — teaching an Agent a preference or procedure that lasts.\n- `spaces` — admitting an Agent to a Space.\n- `store` — publishing an Agent release.",
1679
- "hash": "f5c941dd33a0972a57433e00234c9d6a86e5897c32e950e91b0d40d26142119f",
1679
+ "hash": "accb2eed6a5f6f0fe16c9bec8bd2233ecda3111976e36215e81ea61491ebe93b",
1680
1680
  "tokens": 944,
1681
1681
  "sections": [
1682
1682
  {
@@ -1716,7 +1716,7 @@
1716
1716
  "id": "memory",
1717
1717
  "title": "Where an Agent's knowledge lives",
1718
1718
  "readWhen": "you need to read or change what an Agent knows, or tell its Memory from its configuration",
1719
- "tokens": 858,
1719
+ "tokens": 891,
1720
1720
  "aliases": [],
1721
1721
  "explains": [
1722
1722
  "MEMORY_NOT_MOUNTED",
@@ -1861,9 +1861,9 @@
1861
1861
  "version": 4,
1862
1862
  "routed": true,
1863
1863
  "aliases": [],
1864
- "body": "## Concept model\n\n| Noun | Question it answers |\n| --- | --- |\n| Memory | What does the agent durably know — a fact in a versioned Agent or Space Store? |\n| Lesson | What reviewed instruction applies — a Memory node typed `knowledge`, `preference`, `procedure`, `exemplar`, `rubric` or `capability`? |\n| Lesson state | May it reach runtime context — `candidate` (proposed), `active`, `needs_attention` (stopped because its evidence changed) or `archived` (history only)? |\n| Use receipt | What happened when one exact lesson version was used — `presented`, `applied`, `verified` or `failed`, at the strength the server assigned? |\n| Notepad | What did the agent note for itself — private scratch, never reviewed knowledge? |\n| Skill | What is installed and invoked — the executable artifact a reviewed lesson can compile into? |\n\n## Decide\n\n- A durable **fact** belongs in Memory. A **rule, preference or procedure** that should govern future work belongs in a lesson, and every lesson enters as a `candidate` a person must review.\n- The agent's own working notes belong in its **Notepad** — never something the person asked to stick.\n- A reviewed procedure that should become invocable or publishable becomes a **Skill**.\n- Never use a lesson to grant access: it cannot open a file, install a credential, widen a membership or authorize a tool action.\n\n## Workflow\n\n### Make a rule stick\n\n1. Restate it exactly — the trigger, the action and the exception — and choose its kind.\n2. File it for review. Never write it straight into canonical Memory or your own instructions.\n\nWhere the current Task is filed does not choose a Memory Store. The default below is deliberately\nAgent-owned; use a Space scope only after `skrr memory mounts resolve` confirms that Space Store is\nmounted for this session.\n\n```bash\nskrr memory proposals create --agent <agent-id> --scope agent:<agent-id> \\\n --kind <kind> --path <path> --content \"<the rule as stated>\"\n```\n\n3. Report it as awaiting the person's review — a candidate, not something learned.\n\n### Act on a lesson\n\n1. Only `active` lessons reach runtime context; check `applicability` and `exclusions` before relying on one.\n2. Act under ordinary tool and permission authority — the lesson itself grants nothing.\n3. The runtime records a use receipt against the exact version; a self-report counts as a claim, not an observation.\n\n## Authority\n\n| Actor | May do | Only a person may do |\n| --- | --- | --- |\n| Agent | Propose candidates; reinforce an active lesson whose content did not change; stop using one | Activate, change or restore active instructions |\n| Person | Review, activate and change instructions; dismiss candidates; grant evidence use; install and publish a Skill; own adaptive-learning consent | — |\n\n## Live state\n\n- `skrr memory proposals list --agent <agent-id> --scope <scope>` — proposals awaiting review (`proposed` is the default status).\n- `skrr learning status` — the person's adaptive-learning consent, learned state and serving fallback.\n- `skrr agents notepad show <agent-id>` — the agent's private notes.\n\n## Related guides\n\n- `store` — publishing a lesson as a Skill release, and installing Skills.\n- `agents` — the agent's Memory, Notepad and instructions.",
1865
- "hash": "b228b2fa45eec60cfbf3fb451bcc68061931197d947ed2d7da0cf97251ad6533",
1866
- "tokens": 861,
1864
+ "body": "## Concept model\n\n| Noun | Question it answers |\n| --- | --- |\n| Memory | What does the agent durably know — a fact in a versioned Agent or Space Store? |\n| Lesson | What reviewed instruction applies — a Memory node typed `knowledge`, `preference`, `procedure`, `exemplar`, `rubric` or `capability`? |\n| Lesson state | May it reach runtime context — `candidate` (proposed), `active`, `needs_attention` (stopped because its evidence changed) or `archived` (history only)? |\n| Use receipt | What happened when one exact lesson version was used — `presented`, `applied`, `verified` or `failed`, at the strength the server assigned? |\n| Notepad | What did the agent note for itself — private scratch, never reviewed knowledge? |\n| Skill | What is installed and invoked — the executable artifact a reviewed lesson can compile into? |\n\n## Decide\n\n- A durable **fact** belongs in Memory. A **rule, preference or procedure** that should govern future work belongs in a lesson, and every lesson enters as a `candidate` a person must review.\n- The agent's own working notes belong in its **Notepad** — never something the person asked to stick.\n- A reviewed procedure that should become invocable or publishable becomes a **Skill**.\n- Never use a lesson to grant access: it cannot open a file, install a credential, widen a membership or authorize a tool action.\n\n## Workflow\n\n### Make a rule stick\n\n1. Restate it exactly — the trigger, the action and the exception — and choose its kind.\n2. File it for review. Never write it straight into canonical Memory or your own instructions.\n\nWhere the current Task is filed does not choose a Memory Store. The default below is deliberately\nAgent-owned; use a Space scope only after this session's mounts confirm that Space Store is mounted.\n\n`skrr memory mounts resolve` shows the mounts.\n\n```bash\nskrr memory proposals create --agent <agent-id> --scope agent:<agent-id> \\\n --kind <kind> --path <path> --content \"<the rule as stated>\"\n```\n\n3. Report it as awaiting the person's review — a candidate, not something learned.\n\n### Act on a lesson\n\n1. Only `active` lessons reach runtime context; check `applicability` and `exclusions` before relying on one.\n2. Act under ordinary tool and permission authority — the lesson itself grants nothing.\n3. The runtime records a use receipt against the exact version; a self-report counts as a claim, not an observation.\n\n## Authority\n\n| Actor | May do | Only a person may do |\n| --- | --- | --- |\n| Agent | Propose candidates; reinforce an active lesson whose content did not change; stop using one | Activate, change or restore active instructions |\n| Person | Review, activate and change instructions; dismiss candidates; grant evidence use; install and publish a Skill; own adaptive-learning consent | — |\n\n## Live state\n\n- `skrr memory proposals list --agent <agent-id> --scope <scope>` — proposals awaiting review (`proposed` is the default status).\n- `skrr learning status` — the person's adaptive-learning consent, learned state and serving fallback.\n- `skrr agents notepad show <agent-id>` — the agent's private notes.\n\n## Related guides\n\n- `store` — publishing a lesson as a Skill release, and installing Skills.\n- `agents` — the agent's Memory, Notepad and instructions.",
1865
+ "hash": "6c69c94f8070907742b4513feea16b30aa0f99cf632eb7ef74b85ea8991ed305",
1866
+ "tokens": 865,
1867
1867
  "sections": [
1868
1868
  {
1869
1869
  "id": "model",
@@ -5437,7 +5437,7 @@
5437
5437
  "id": "memory",
5438
5438
  "title": "Where an Agent's knowledge lives",
5439
5439
  "readWhen": "you need to read or change what an Agent knows, or tell its Memory from its configuration",
5440
- "tokens": 858,
5440
+ "tokens": 891,
5441
5441
  "aliases": [],
5442
5442
  "explains": [
5443
5443
  "MEMORY_NOT_MOUNTED",
@@ -5513,7 +5513,7 @@
5513
5513
  "id": "memory",
5514
5514
  "title": "Where an Agent's knowledge lives",
5515
5515
  "readWhen": "you need to read or change what an Agent knows, or tell its Memory from its configuration",
5516
- "tokens": 858,
5516
+ "tokens": 891,
5517
5517
  "aliases": [],
5518
5518
  "explains": [
5519
5519
  "MEMORY_NOT_MOUNTED",
@@ -5590,7 +5590,7 @@
5590
5590
  "id": "memory",
5591
5591
  "title": "Where an Agent's knowledge lives",
5592
5592
  "readWhen": "you need to read or change what an Agent knows, or tell its Memory from its configuration",
5593
- "tokens": 858,
5593
+ "tokens": 891,
5594
5594
  "aliases": [],
5595
5595
  "explains": [
5596
5596
  "MEMORY_NOT_MOUNTED",
@@ -5625,9 +5625,9 @@
5625
5625
  "MEMORY_VERSION_CONFLICT",
5626
5626
  "NODE_REFERENCE_REQUIRED"
5627
5627
  ],
5628
- "body": "Read this when you need to read or change what an Agent knows, or tell its Memory from its configuration.\n\n| Where | What it holds | Who changes it |\n| ------------------ | -------------------------------------------------------------------------- | ---------------------------------------- |\n| Agent Memory Store | What this Agent knows, as versioned topics | The Agent, through its own mounted store |\n| Space Memory Store | What a Space knows; mounted only for a member of that Space | An Agent proposes; a person approves |\n| Notepad | One person's private notes to this Agent, read on that person's chat turns | That person |\n| Persona files | Who the Agent is and who it works for (SOUL, IDENTITY, USER and others) | The owner, or the Agent about itself |\n\nInstructions and tone are configuration, not Memory: what the Agent must always do goes in an\ninstruction block, and how it writes goes in its tone.\n\nRules:\n\n- Resolve the mounts before you read or write. A store mounted read-only takes neither writes nor\n proposals from you: keep the topic in the Agent's own store, where a person can promote it.\n- A Task or command's `--space` chooses where that work is filed and supplies request context; it\n does **not** select or mount a Space Memory Store. Run `skrr memory mounts resolve` first, then\n name one of the mounted scopes. When the requested Space Store is absent, use the Agent Store\n only for Agent-owned knowledge; never treat that fallback as shared Space Memory.\n- An Agent's change to a Space Memory Store is always a proposal. It changes nothing until a\n person approves it, and only a person may approve promoting an Agent topic into a Space or\n admitting personal data to one.\n- A change to an existing topic names the version it read. When the topic has moved on, read the\n latest version and retry against it; never overwrite it.\n- Everyone with edit access to the Agent can read its Agent Memory Store.\n- What the Agent may remember (read and write scopes, retention, evidence) is governance, and\n governance is the owner's.\n\nUse the canonical commands: `skrr memory mounts`, `skrr memory topics`, `skrr memory proposals`,\n`skrr memory versions`. The ungrouped commands such as `skrr memory list` and `skrr memory tree`\naddress older pools: what one surface holds does not exist in the other. `--agent` selects whose\nMemory you act on; it does not change who is signed in.\n\n## Example\n\nFind what the Agent already knows before adding to it, then propose the addition.\n\n```bash\nskrr memory mounts resolve --agent <agent-id> --space <space-id> --json\nskrr memory topics list --agent <agent-id> --space <space-id> --scope \"space:<space-id>\"\nskrr memory proposals create --agent <agent-id> --space <space-id> --scope \"space:<space-id>\" \\\n --path /team/release-process --content \"Releases need a green smoke test.\" --evidence task:<task-id>\n```\n\n## Refusals\n\n| Code | Cause | Fix |\n| -------------------------------- | ------------------------------------------------ | ------------------------------------------------------- |\n| `MEMORY_NOT_MOUNTED` | The target store is not mounted for this session; `--space` alone does not mount it | Run `memory mounts resolve`; choose a writable mounted scope, or use Agent-owned Memory without claiming it is shared |\n| `MEMORY_STORE_READ_ONLY` | The target store is mounted read-only | Write to the Agent's own store; a person can promote it |\n| `MEMORY_SPACE_PROPOSAL_REQUIRED` | An Agent wrote straight to a Space Memory Store | Submit it as a proposal |\n| `MEMORY_VERSION_CONFLICT` | The topic changed since you read it | Read the latest version and retry against it |\n| `NODE_REFERENCE_REQUIRED` | The request named no topic | Pass one of `memoryId`, `nodeId` or `path` |",
5629
- "hash": "4ff05f37d8557901fb27c3cdfc3251c006554f7c74476952dac37cbea5a5610b",
5630
- "tokens": 858,
5628
+ "body": "Read this when you need to read or change what an Agent knows, or tell its Memory from its configuration.\n\n| Where | What it holds | Who changes it |\n| ------------------ | -------------------------------------------------------------------------- | ---------------------------------------- |\n| Agent Memory Store | What this Agent knows, as versioned topics | The Agent, through its own mounted store |\n| Space Memory Store | What a Space knows; mounted only for a member of that Space | An Agent proposes; a person approves |\n| Notepad | One person's private notes to this Agent, read on that person's chat turns | That person |\n| Persona files | Who the Agent is and who it works for (SOUL, IDENTITY, USER and others) | The owner, or the Agent about itself |\n\nInstructions and tone are configuration, not Memory: what the Agent must always do goes in an\ninstruction block, and how it writes goes in its tone.\n\nRules:\n\n- Resolve the mounts before you read or write. A store mounted read-only takes neither writes nor\n proposals from you: keep the topic in the Agent's own store, where a person can promote it.\n- The Space a Task or call names chooses where that work is filed and supplies request context; it\n does **not** select or mount a Space Memory Store. Resolve the mounts first, then name one of the\n mounted scopes. When the requested Space Store is absent, use the Agent Store only for\n Agent-owned knowledge; never treat that fallback as shared Space Memory.\n- An Agent's change to a Space Memory Store is always a proposal. It changes nothing until a\n person approves it, and only a person may approve promoting an Agent topic into a Space or\n admitting personal data to one.\n- A change to an existing topic names the version it read. When the topic has moved on, read the\n latest version and retry against it; never overwrite it.\n- Everyone with edit access to the Agent can read its Agent Memory Store.\n- What the Agent may remember (read and write scopes, retention, evidence) is governance, and\n governance is the owner's.\n\nUse the canonical commands: `skrr memory mounts`, `skrr memory topics`, `skrr memory proposals`,\n`skrr memory versions`. The ungrouped commands such as `skrr memory list` and `skrr memory tree`\naddress older pools: what one surface holds does not exist in the other. `--agent` selects whose\nMemory you act on; it does not change who is signed in. `--space` chooses where a command's work is\nfiled and never mounts a store: run `skrr memory mounts resolve` first, then pass one of the\nmounted scopes with `--scope`.\n\n## Example\n\nFind what the Agent already knows before adding to it, then propose the addition.\n\n```bash\nskrr memory mounts resolve --agent <agent-id> --space <space-id> --json\nskrr memory topics list --agent <agent-id> --space <space-id> --scope \"space:<space-id>\"\nskrr memory proposals create --agent <agent-id> --space <space-id> --scope \"space:<space-id>\" \\\n --path /team/release-process --content \"Releases need a green smoke test.\" --evidence task:<task-id>\n```\n\n## Refusals\n\n| Code | Cause | Fix |\n| -------------------------------- | ------------------------------------------------ | ------------------------------------------------------- |\n| `MEMORY_NOT_MOUNTED` | The target store is not mounted for this session; naming a Space alone does not mount it | Resolve the mounts; choose a writable mounted scope, or use Agent-owned Memory without claiming it is shared |\n| `MEMORY_STORE_READ_ONLY` | The target store is mounted read-only | Write to the Agent's own store; a person can promote it |\n| `MEMORY_SPACE_PROPOSAL_REQUIRED` | An Agent wrote straight to a Space Memory Store | Submit it as a proposal |\n| `MEMORY_VERSION_CONFLICT` | The topic changed since you read it | Read the latest version and retry against it |\n| `NODE_REFERENCE_REQUIRED` | The request named no topic | Pass one of `memoryId`, `nodeId` or `path` |",
5629
+ "hash": "8a5bafc440947aa93782c6d0b196c08f83a0656c4eafd1439990ba99f35c30ce",
5630
+ "tokens": 891,
5631
5631
  "sections": [
5632
5632
  {
5633
5633
  "id": "model",
@@ -5666,7 +5666,7 @@
5666
5666
  "id": "memory",
5667
5667
  "title": "Where an Agent's knowledge lives",
5668
5668
  "readWhen": "you need to read or change what an Agent knows, or tell its Memory from its configuration",
5669
- "tokens": 858,
5669
+ "tokens": 891,
5670
5670
  "aliases": [],
5671
5671
  "explains": [
5672
5672
  "MEMORY_NOT_MOUNTED",
@@ -5741,7 +5741,7 @@
5741
5741
  "id": "memory",
5742
5742
  "title": "Where an Agent's knowledge lives",
5743
5743
  "readWhen": "you need to read or change what an Agent knows, or tell its Memory from its configuration",
5744
- "tokens": 858,
5744
+ "tokens": 891,
5745
5745
  "aliases": [],
5746
5746
  "explains": [
5747
5747
  "MEMORY_NOT_MOUNTED",
@@ -775,6 +775,12 @@ function endsWithIgnoringWhitespace(whole, tail) {
775
775
  const wanted = solid(tail);
776
776
  return wanted.length > 0 && solid(whole).endsWith(wanted);
777
777
  }
778
+ /** `head` is the start of `whole`, whitespace aside. */
779
+ function startsWithIgnoringWhitespace(whole, head) {
780
+ const solid = (value) => value.replace(/\s+/g, '');
781
+ const wanted = solid(head);
782
+ return wanted.length > 0 && solid(whole).startsWith(wanted);
783
+ }
778
784
  class AgenticStreamClient {
779
785
  options;
780
786
  socket = null;
@@ -2456,10 +2462,18 @@ class AgenticStreamClient {
2456
2462
  // `' \n\n::::skrr-ui{'`), so the harness's last block is not a byte-for-byte suffix of the
2457
2463
  // accumulation even though it is its tail, and taking it as the answer would drop
2458
2464
  // everything the agent said before the tool call (OSK-16934).
2465
+ //
2466
+ // The other way round is the same defect from the other end: a result that is a strict
2467
+ // PREFIX of the accumulation was cut to a length cap, not written as the answer. The daemon
2468
+ // keeps the first 4000 characters of a turn's text for the result it reports, so every local
2469
+ // answer longer than that came back as its first 4000 characters, cut mid-word, with
2470
+ // `status: completed` (OSK-20616). What streamed is the longer record of the same words.
2459
2471
  const accumulationIsSuperset = finalText.length > 0 &&
2460
2472
  this.accumulated.length > finalText.length &&
2461
2473
  (this.accumulated.endsWith(finalText) ||
2462
- endsWithIgnoringWhitespace(this.accumulated, finalText));
2474
+ endsWithIgnoringWhitespace(this.accumulated, finalText) ||
2475
+ this.accumulated.startsWith(finalText) ||
2476
+ startsWithIgnoringWhitespace(this.accumulated, finalText));
2463
2477
  if (event.publicProjection === 'lookback_answer_v1' && typeof event.result === 'string') {
2464
2478
  this.accumulated = event.result;
2465
2479
  }
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ // Content-addressed source-envelope manifest. A manifest is completeness
4
+ // evidence only after an authenticated native response binds it to its exact
5
+ // dispatch, and every listed envelope has been independently retained.
6
+ const { createHash } = require('node:crypto');
7
+ const MAX_EVENTS = 1024;
8
+ const MAX_EVENT_BYTES = 256 * 1024;
9
+ const MAX_TOTAL_BYTES = 8 * 1024 * 1024;
10
+ const HASH = /^[0-9a-f]{64}$/;
11
+ const id = value => typeof value === 'string' && value.length > 0 && value.length <= 240;
12
+ const digest = value => createHash('sha256').update(value, 'utf8').digest('hex');
13
+
14
+ function sorted(value) {
15
+ if (Array.isArray(value)) return value.map(sorted);
16
+ if (value && typeof value === 'object') return Object.fromEntries(
17
+ Object.keys(value).sort().map(key => [key, sorted(value[key])]));
18
+ return value;
19
+ }
20
+ function normalize(envelope) {
21
+ // JSON round-trip defines the wire representation, rejects cycles, and
22
+ // removes undefined properties in the same way as the uploader.
23
+ const value = JSON.parse(JSON.stringify(envelope));
24
+ if (!value || typeof value !== 'object' || Array.isArray(value) ||
25
+ !id(value.id) || !value.ev || typeof value.ev !== 'object' || Array.isArray(value.ev))
26
+ throw new TypeError('Invalid scheduled stream envelope');
27
+ // These fields are owned by the server or are independently authenticated
28
+ // native receipts, not part of the replayable provider stream.
29
+ for (const key of ['__seq', 'userSeq', '__degraded', 'messageId', 'managedEventType']) delete value[key];
30
+ for (const key of ['autonomousSourceExecutorTerminal', 'autonomousSourceExecutorNotStarted',
31
+ 'ordinaryExecutorTerminal', 'personalContextInvocationTerminal', 'personalContextInvocationNotStarted'])
32
+ delete value.ev[key];
33
+ return JSON.stringify(sorted(value));
34
+ }
35
+ function fingerprint(envelope) {
36
+ const json = normalize(envelope);
37
+ const bytes = Buffer.byteLength(json, 'utf8');
38
+ if (bytes > MAX_EVENT_BYTES) throw new RangeError('Scheduled stream envelope exceeds the limit');
39
+ return { hash: digest(json), bytes, json };
40
+ }
41
+ function create(binding) {
42
+ if (!binding || ![binding.runId, binding.sessionId, binding.requestId, binding.dispatchId].every(id))
43
+ throw new TypeError('Invalid scheduled stream binding');
44
+ let closed = false;
45
+ let unavailable = null;
46
+ let totalBytes = 0;
47
+ const hashes = [];
48
+ return {
49
+ add(envelopes) {
50
+ if (closed) throw new Error('Scheduled stream manifest is already closed');
51
+ if (unavailable || !Array.isArray(envelopes)) return;
52
+ for (const envelope of envelopes) {
53
+ if (hashes.length >= MAX_EVENTS) { unavailable = 'event_limit'; break; }
54
+ let entry;
55
+ try { entry = fingerprint(envelope); }
56
+ catch { unavailable = 'invalid_or_oversize_event'; break; }
57
+ if (totalBytes + entry.bytes > MAX_TOTAL_BYTES) { unavailable = 'byte_limit'; break; }
58
+ hashes.push(entry.hash);
59
+ totalBytes += entry.bytes;
60
+ }
61
+ // A partial prefix is never a manifest.
62
+ if (unavailable) { hashes.length = 0; totalBytes = 0; }
63
+ },
64
+ finish() {
65
+ if (closed) throw new Error('Scheduled stream manifest is already closed');
66
+ closed = true;
67
+ if (unavailable) return { unavailable };
68
+ return { manifest: { version: 1, algorithm: 'sha256', runId: binding.runId,
69
+ sessionId: binding.sessionId, requestId: binding.requestId, dispatchId: binding.dispatchId,
70
+ count: hashes.length, totalBytes, hashes: [...hashes] } };
71
+ },
72
+ };
73
+ }
74
+ function validate(manifest, binding) {
75
+ return Boolean(manifest && typeof manifest === 'object' && !Array.isArray(manifest) &&
76
+ Object.keys(manifest).sort().join(',') === 'algorithm,count,dispatchId,hashes,requestId,runId,sessionId,totalBytes,version' &&
77
+ manifest.version === 1 && manifest.algorithm === 'sha256' &&
78
+ ['runId', 'sessionId', 'requestId', 'dispatchId'].every(key => id(binding?.[key]) && manifest[key] === binding[key]) &&
79
+ Number.isSafeInteger(manifest.count) && manifest.count >= 0 && manifest.count <= MAX_EVENTS &&
80
+ Number.isSafeInteger(manifest.totalBytes) && manifest.totalBytes >= 0 && manifest.totalBytes <= MAX_TOTAL_BYTES &&
81
+ Array.isArray(manifest.hashes) && manifest.hashes.length === manifest.count &&
82
+ manifest.hashes.every(value => typeof value === 'string' && HASH.test(value)));
83
+ }
84
+ function verify(manifest, binding, envelopes) {
85
+ if (!validate(manifest, binding) || !Array.isArray(envelopes) || envelopes.length !== manifest.count)
86
+ return false;
87
+ let totalBytes = 0;
88
+ try {
89
+ for (let index = 0; index < envelopes.length; index += 1) {
90
+ const entry = fingerprint(envelopes[index]);
91
+ if (entry.hash !== manifest.hashes[index]) return false;
92
+ totalBytes += entry.bytes;
93
+ if (totalBytes > MAX_TOTAL_BYTES) return false;
94
+ }
95
+ } catch { return false; }
96
+ return totalBytes === manifest.totalBytes;
97
+ }
98
+ module.exports = { MAX_EVENTS, MAX_EVENT_BYTES, MAX_TOTAL_BYTES, normalize, fingerprint, create, validate, verify };
@@ -0,0 +1,15 @@
1
+ export interface ScheduledStreamBinding { runId: string; sessionId: string; requestId: string; dispatchId: string }
2
+ export interface ScheduledStreamManifest extends ScheduledStreamBinding {
3
+ version: 1; algorithm: 'sha256'; count: number; totalBytes: number; hashes: string[];
4
+ }
5
+ export const MAX_EVENTS: number;
6
+ export const MAX_EVENT_BYTES: number;
7
+ export const MAX_TOTAL_BYTES: number;
8
+ export function normalize(envelope: unknown): string;
9
+ export function fingerprint(envelope: unknown): { hash: string; bytes: number; json: string };
10
+ export function create(binding: ScheduledStreamBinding): {
11
+ add(envelopes: unknown): void;
12
+ finish(): { manifest: ScheduledStreamManifest } | { unavailable: string };
13
+ };
14
+ export function validate(manifest: unknown, binding: ScheduledStreamBinding): boolean;
15
+ export function verify(manifest: unknown, binding: ScheduledStreamBinding, envelopes: unknown): boolean;