larkin 0.4.1 → 0.4.5

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 (42) hide show
  1. package/README.md +76 -33
  2. package/artifacts/release/THIRD_PARTY_NOTICES.txt +218 -228
  3. package/assets/readme/larkin-github-cover.png +0 -0
  4. package/assets/readme/larkin-hero.svg +111 -0
  5. package/dist/agent/agent-state-store.cjs +193 -1
  6. package/dist/agent/agent-state-store.mjs +191 -0
  7. package/dist/app/agent-config.mjs +73 -6
  8. package/dist/app/agent-readiness.cjs +22 -2
  9. package/dist/app/agent-readiness.mjs +20 -2
  10. package/dist/app/binary-entry.mjs +6 -0
  11. package/dist/app/cli.mjs +7 -1
  12. package/dist/app/local-control.mjs +108 -2
  13. package/dist/app/run.mjs +8 -0
  14. package/dist/app/runtime-process.mjs +17 -1
  15. package/dist/app/session-cli.mjs +55 -20
  16. package/dist/dashboard/dashboard-view-model.mjs +12 -3
  17. package/dist/feishu/host-shell.cjs +104 -9
  18. package/dist/feishu/host-shell.mjs +105 -10
  19. package/dist/platform/config.cjs +406 -33
  20. package/dist/platform/config.mjs +405 -33
  21. package/dist/platform/process-boundary-trace.cjs +108 -0
  22. package/dist/platform/process-boundary-trace.mjs +102 -0
  23. package/dist/runtime/pi-artifact-provenance.cjs +147 -0
  24. package/dist/runtime/pi-artifact-provenance.mjs +139 -0
  25. package/dist/runtime/pi-compaction-recovery.cjs +703 -0
  26. package/dist/runtime/pi-compaction-recovery.mjs +679 -0
  27. package/dist/runtime/pi-model-catalog.cjs +2 -2
  28. package/dist/runtime/pi-model-catalog.mjs +2 -2
  29. package/dist/runtime/pi-profile-migration.cjs +556 -0
  30. package/dist/runtime/pi-profile-migration.mjs +515 -0
  31. package/dist/runtime/pi-provider-config.cjs +3 -3
  32. package/dist/runtime/pi-provider-config.mjs +3 -3
  33. package/dist/runtime/pi-rpc-client.cjs +39 -22
  34. package/dist/runtime/pi-rpc-client.mjs +39 -22
  35. package/dist/runtime/provider-error-classifier.cjs +39 -0
  36. package/dist/runtime/provider-error-classifier.mjs +34 -0
  37. package/dist/runtime/runtime-adapters.mjs +176 -27
  38. package/dist/runtime/runtime-host.cjs +630 -19
  39. package/dist/runtime/runtime-host.mjs +628 -18
  40. package/dist/runtime/runtime-readiness.cjs +6 -4
  41. package/dist/runtime/runtime-readiness.mjs +6 -4
  42. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PiRpcClient = void 0;
4
- /** A strict, bounded LF-delimited client for Pi's public RPC protocol. */
4
+ /** A strict LF-delimited client for Pi's public RPC protocol. */
5
5
  class PiRpcClient {
6
6
  process;
7
7
  pending = new Map();
@@ -11,9 +11,10 @@ class PiRpcClient {
11
11
  inputTimeoutMs;
12
12
  inputProgressTimeoutMs;
13
13
  inputMaxTimeoutMs;
14
- maxFrameBytes;
15
14
  maxStderrBytes;
16
15
  shutdownGraceMs;
16
+ compactTimeoutMs;
17
+ expiredRequestIds = new Set();
17
18
  stdoutBuffer = Buffer.alloc(0);
18
19
  stderrBuffer = Buffer.alloc(0);
19
20
  exited = false;
@@ -29,9 +30,11 @@ class PiRpcClient {
29
30
  if (this.requestTimeoutMs <= 0 || this.inputTimeoutMs <= 0 || this.inputProgressTimeoutMs <= 0
30
31
  || this.inputMaxTimeoutMs < this.inputTimeoutMs)
31
32
  throw new Error("Pi RPC timeout options are invalid");
32
- this.maxFrameBytes = options.maxFrameBytes ?? 1024 * 1024;
33
33
  this.maxStderrBytes = options.maxStderrBytes ?? 64 * 1024;
34
34
  this.shutdownGraceMs = options.shutdownGraceMs ?? 2_000;
35
+ this.compactTimeoutMs = options.compactTimeoutMs ?? 120_000;
36
+ if (this.compactTimeoutMs <= 0)
37
+ throw new Error("Pi compact timeout must be positive");
35
38
  process.stdout?.on("data", (chunk) => this.consume(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
36
39
  process.stdout?.on("end", () => {
37
40
  if (this.stdoutBuffer.length)
@@ -61,19 +64,26 @@ class PiRpcClient {
61
64
  return () => this.failureListeners.delete(listener);
62
65
  }
63
66
  request(command, fields = {}) {
67
+ const input = isInputCommand(command);
68
+ return this.requestInternal(command, fields, input ? this.inputTimeoutMs : this.requestTimeoutMs, input ? Date.now() + this.inputMaxTimeoutMs : undefined, true);
69
+ }
70
+ /** Send the documented compact RPC with a Larkin-owned absolute deadline. */
71
+ requestCompact(customInstructions) {
72
+ const startedAt = Date.now();
73
+ return this.requestInternal("compact", customInstructions === undefined ? {} : { customInstructions }, this.compactTimeoutMs, startedAt + this.compactTimeoutMs, false);
74
+ }
75
+ requestInternal(command, fields, timeoutMs, absoluteDeadlineAt, failClientOnTimeout) {
64
76
  if (this.failed)
65
77
  return Promise.reject(this.failed);
66
78
  const id = `larkin-${++this.nextId}`;
67
79
  return new Promise((resolve, reject) => {
68
80
  const startedAt = Date.now();
69
- const input = isInputCommand(command);
70
81
  const pending = {
71
- command, startedAt,
72
- ...(input ? { absoluteDeadlineAt: startedAt + this.inputMaxTimeoutMs } : {}),
73
- resolve: resolve, reject,
82
+ command, startedAt, ...(absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt } : {}),
83
+ failClientOnTimeout, resolve: resolve, reject,
74
84
  };
75
85
  this.pending.set(id, pending);
76
- this.armTimeout(id, pending, input ? this.inputTimeoutMs : this.requestTimeoutMs);
86
+ this.armTimeout(id, pending, timeoutMs);
77
87
  try {
78
88
  if (!this.process.stdin || this.process.stdin.destroyed)
79
89
  throw new Error("stdin is unavailable");
@@ -103,15 +113,8 @@ class PiRpcClient {
103
113
  this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, chunk]);
104
114
  while (true) {
105
115
  const newline = this.stdoutBuffer.indexOf(0x0a);
106
- if (newline < 0) {
107
- if (this.stdoutBuffer.length > this.maxFrameBytes)
108
- this.protocolFailure(`frame exceeds ${this.maxFrameBytes} bytes`);
109
- return;
110
- }
111
- if (newline > this.maxFrameBytes) {
112
- this.protocolFailure(`frame exceeds ${this.maxFrameBytes} bytes`);
116
+ if (newline < 0)
113
117
  return;
114
- }
115
118
  let frame = this.stdoutBuffer.subarray(0, newline);
116
119
  this.stdoutBuffer = this.stdoutBuffer.subarray(newline + 1);
117
120
  if (frame.at(-1) === 0x0d)
@@ -140,6 +143,8 @@ class PiRpcClient {
140
143
  if (message.type === "response" && typeof message.id === "string") {
141
144
  const pending = this.pending.get(message.id);
142
145
  if (!pending) {
146
+ if (this.expiredRequestIds.delete(message.id))
147
+ return;
143
148
  this.protocolFailure(`unexpected response id ${message.id}`);
144
149
  return;
145
150
  }
@@ -181,12 +186,24 @@ class PiRpcClient {
181
186
  return;
182
187
  const elapsed = Date.now() - pending.startedAt;
183
188
  const absolute = pending.absoluteDeadlineAt !== undefined && Date.now() >= pending.absoluteDeadlineAt;
184
- const error = isInputCommand(pending.command)
185
- ? new Error(absolute
186
- ? `Pi RPC ${pending.command} preflight timed out at absolute ${this.inputMaxTimeoutMs}ms limit`
187
- : `Pi RPC ${pending.command} preflight timed out after ${elapsed}ms without progress`)
188
- : new Error(`Pi RPC ${pending.command} timed out after ${this.requestTimeoutMs}ms`);
189
- this.fail(error, true);
189
+ this.pending.delete(id);
190
+ this.expiredRequestIds.add(id);
191
+ while (this.expiredRequestIds.size > 256) {
192
+ const oldest = this.expiredRequestIds.values().next().value;
193
+ if (!oldest)
194
+ break;
195
+ this.expiredRequestIds.delete(oldest);
196
+ }
197
+ const error = pending.command === "compact"
198
+ ? new Error(`Pi RPC compact timed out at absolute compact deadline ${elapsed}ms`)
199
+ : isInputCommand(pending.command)
200
+ ? new Error(absolute
201
+ ? `Pi RPC ${pending.command} preflight timed out at absolute ${this.inputMaxTimeoutMs}ms limit`
202
+ : `Pi RPC ${pending.command} preflight timed out after ${elapsed}ms without progress`)
203
+ : new Error(`Pi RPC ${pending.command} timed out after ${this.requestTimeoutMs}ms`);
204
+ pending.reject(error);
205
+ if (pending.failClientOnTimeout !== false)
206
+ this.fail(error, true);
190
207
  }, delay);
191
208
  pending.timer.unref?.();
192
209
  }
@@ -1,4 +1,4 @@
1
- /** A strict, bounded LF-delimited client for Pi's public RPC protocol. */
1
+ /** A strict LF-delimited client for Pi's public RPC protocol. */
2
2
  export class PiRpcClient {
3
3
  process;
4
4
  pending = new Map();
@@ -8,9 +8,10 @@ export class PiRpcClient {
8
8
  inputTimeoutMs;
9
9
  inputProgressTimeoutMs;
10
10
  inputMaxTimeoutMs;
11
- maxFrameBytes;
12
11
  maxStderrBytes;
13
12
  shutdownGraceMs;
13
+ compactTimeoutMs;
14
+ expiredRequestIds = new Set();
14
15
  stdoutBuffer = Buffer.alloc(0);
15
16
  stderrBuffer = Buffer.alloc(0);
16
17
  exited = false;
@@ -26,9 +27,11 @@ export class PiRpcClient {
26
27
  if (this.requestTimeoutMs <= 0 || this.inputTimeoutMs <= 0 || this.inputProgressTimeoutMs <= 0
27
28
  || this.inputMaxTimeoutMs < this.inputTimeoutMs)
28
29
  throw new Error("Pi RPC timeout options are invalid");
29
- this.maxFrameBytes = options.maxFrameBytes ?? 1024 * 1024;
30
30
  this.maxStderrBytes = options.maxStderrBytes ?? 64 * 1024;
31
31
  this.shutdownGraceMs = options.shutdownGraceMs ?? 2_000;
32
+ this.compactTimeoutMs = options.compactTimeoutMs ?? 120_000;
33
+ if (this.compactTimeoutMs <= 0)
34
+ throw new Error("Pi compact timeout must be positive");
32
35
  process.stdout?.on("data", (chunk) => this.consume(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
33
36
  process.stdout?.on("end", () => {
34
37
  if (this.stdoutBuffer.length)
@@ -58,19 +61,26 @@ export class PiRpcClient {
58
61
  return () => this.failureListeners.delete(listener);
59
62
  }
60
63
  request(command, fields = {}) {
64
+ const input = isInputCommand(command);
65
+ return this.requestInternal(command, fields, input ? this.inputTimeoutMs : this.requestTimeoutMs, input ? Date.now() + this.inputMaxTimeoutMs : undefined, true);
66
+ }
67
+ /** Send the documented compact RPC with a Larkin-owned absolute deadline. */
68
+ requestCompact(customInstructions) {
69
+ const startedAt = Date.now();
70
+ return this.requestInternal("compact", customInstructions === undefined ? {} : { customInstructions }, this.compactTimeoutMs, startedAt + this.compactTimeoutMs, false);
71
+ }
72
+ requestInternal(command, fields, timeoutMs, absoluteDeadlineAt, failClientOnTimeout) {
61
73
  if (this.failed)
62
74
  return Promise.reject(this.failed);
63
75
  const id = `larkin-${++this.nextId}`;
64
76
  return new Promise((resolve, reject) => {
65
77
  const startedAt = Date.now();
66
- const input = isInputCommand(command);
67
78
  const pending = {
68
- command, startedAt,
69
- ...(input ? { absoluteDeadlineAt: startedAt + this.inputMaxTimeoutMs } : {}),
70
- resolve: resolve, reject,
79
+ command, startedAt, ...(absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt } : {}),
80
+ failClientOnTimeout, resolve: resolve, reject,
71
81
  };
72
82
  this.pending.set(id, pending);
73
- this.armTimeout(id, pending, input ? this.inputTimeoutMs : this.requestTimeoutMs);
83
+ this.armTimeout(id, pending, timeoutMs);
74
84
  try {
75
85
  if (!this.process.stdin || this.process.stdin.destroyed)
76
86
  throw new Error("stdin is unavailable");
@@ -100,15 +110,8 @@ export class PiRpcClient {
100
110
  this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, chunk]);
101
111
  while (true) {
102
112
  const newline = this.stdoutBuffer.indexOf(0x0a);
103
- if (newline < 0) {
104
- if (this.stdoutBuffer.length > this.maxFrameBytes)
105
- this.protocolFailure(`frame exceeds ${this.maxFrameBytes} bytes`);
106
- return;
107
- }
108
- if (newline > this.maxFrameBytes) {
109
- this.protocolFailure(`frame exceeds ${this.maxFrameBytes} bytes`);
113
+ if (newline < 0)
110
114
  return;
111
- }
112
115
  let frame = this.stdoutBuffer.subarray(0, newline);
113
116
  this.stdoutBuffer = this.stdoutBuffer.subarray(newline + 1);
114
117
  if (frame.at(-1) === 0x0d)
@@ -137,6 +140,8 @@ export class PiRpcClient {
137
140
  if (message.type === "response" && typeof message.id === "string") {
138
141
  const pending = this.pending.get(message.id);
139
142
  if (!pending) {
143
+ if (this.expiredRequestIds.delete(message.id))
144
+ return;
140
145
  this.protocolFailure(`unexpected response id ${message.id}`);
141
146
  return;
142
147
  }
@@ -178,12 +183,24 @@ export class PiRpcClient {
178
183
  return;
179
184
  const elapsed = Date.now() - pending.startedAt;
180
185
  const absolute = pending.absoluteDeadlineAt !== undefined && Date.now() >= pending.absoluteDeadlineAt;
181
- const error = isInputCommand(pending.command)
182
- ? new Error(absolute
183
- ? `Pi RPC ${pending.command} preflight timed out at absolute ${this.inputMaxTimeoutMs}ms limit`
184
- : `Pi RPC ${pending.command} preflight timed out after ${elapsed}ms without progress`)
185
- : new Error(`Pi RPC ${pending.command} timed out after ${this.requestTimeoutMs}ms`);
186
- this.fail(error, true);
186
+ this.pending.delete(id);
187
+ this.expiredRequestIds.add(id);
188
+ while (this.expiredRequestIds.size > 256) {
189
+ const oldest = this.expiredRequestIds.values().next().value;
190
+ if (!oldest)
191
+ break;
192
+ this.expiredRequestIds.delete(oldest);
193
+ }
194
+ const error = pending.command === "compact"
195
+ ? new Error(`Pi RPC compact timed out at absolute compact deadline ${elapsed}ms`)
196
+ : isInputCommand(pending.command)
197
+ ? new Error(absolute
198
+ ? `Pi RPC ${pending.command} preflight timed out at absolute ${this.inputMaxTimeoutMs}ms limit`
199
+ : `Pi RPC ${pending.command} preflight timed out after ${elapsed}ms without progress`)
200
+ : new Error(`Pi RPC ${pending.command} timed out after ${this.requestTimeoutMs}ms`);
201
+ pending.reject(error);
202
+ if (pending.failClientOnTimeout !== false)
203
+ this.fail(error, true);
187
204
  }, delay);
188
205
  pending.timer.unref?.();
189
206
  }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = exports.CANONICAL_CONTEXT_WINDOW_MESSAGE = exports.CODEX_ERROR_PREFIX = void 0;
4
+ exports.buildStrictProviderErrorInput = buildStrictProviderErrorInput;
5
+ exports.classifyStrictProviderError = classifyStrictProviderError;
6
+ exports.CODEX_ERROR_PREFIX = "Codex error: ";
7
+ exports.CANONICAL_CONTEXT_WINDOW_MESSAGE = "Your input exceeds the context window of this model. Please adjust your input and try again.";
8
+ exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = "provider rejected the input because the context window was exceeded";
9
+ /** Build the one structured provider-error shape shared by ingestion and recovery. */
10
+ function buildStrictProviderErrorInput(source) {
11
+ const upstream = source.upstream && typeof source.upstream === "object" && !Array.isArray(source.upstream)
12
+ ? source.upstream : {};
13
+ return {
14
+ message: source.reason !== undefined ? source.reason : source.message,
15
+ code: source.code ?? upstream.code,
16
+ type: source.type ?? upstream.type,
17
+ errorCategory: source.errorCategory,
18
+ };
19
+ }
20
+ /**
21
+ * The recovery path intentionally recognizes only the complete canonical
22
+ * provider message, or the exact internal projection when its category is
23
+ * persisted alongside it. The Codex wrapper is the only normalization
24
+ * permitted for the provider message.
25
+ */
26
+ function classifyStrictProviderError(error) {
27
+ if (typeof error.message !== "string")
28
+ return undefined;
29
+ if (error.errorCategory !== undefined && error.errorCategory !== "context_window")
30
+ return undefined;
31
+ const message = error.message.startsWith(exports.CODEX_ERROR_PREFIX)
32
+ ? error.message.slice(exports.CODEX_ERROR_PREFIX.length) : error.message;
33
+ if (message === exports.CANONICAL_CONTEXT_WINDOW_MESSAGE)
34
+ return "context_window";
35
+ if (error.errorCategory === "context_window" && error.message === exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON) {
36
+ return "context_window";
37
+ }
38
+ return undefined;
39
+ }
@@ -0,0 +1,34 @@
1
+ export const CODEX_ERROR_PREFIX = "Codex error: ";
2
+ export const CANONICAL_CONTEXT_WINDOW_MESSAGE = "Your input exceeds the context window of this model. Please adjust your input and try again.";
3
+ export const INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = "provider rejected the input because the context window was exceeded";
4
+ /** Build the one structured provider-error shape shared by ingestion and recovery. */
5
+ export function buildStrictProviderErrorInput(source) {
6
+ const upstream = source.upstream && typeof source.upstream === "object" && !Array.isArray(source.upstream)
7
+ ? source.upstream : {};
8
+ return {
9
+ message: source.reason !== undefined ? source.reason : source.message,
10
+ code: source.code ?? upstream.code,
11
+ type: source.type ?? upstream.type,
12
+ errorCategory: source.errorCategory,
13
+ };
14
+ }
15
+ /**
16
+ * The recovery path intentionally recognizes only the complete canonical
17
+ * provider message, or the exact internal projection when its category is
18
+ * persisted alongside it. The Codex wrapper is the only normalization
19
+ * permitted for the provider message.
20
+ */
21
+ export function classifyStrictProviderError(error) {
22
+ if (typeof error.message !== "string")
23
+ return undefined;
24
+ if (error.errorCategory !== undefined && error.errorCategory !== "context_window")
25
+ return undefined;
26
+ const message = error.message.startsWith(CODEX_ERROR_PREFIX)
27
+ ? error.message.slice(CODEX_ERROR_PREFIX.length) : error.message;
28
+ if (message === CANONICAL_CONTEXT_WINDOW_MESSAGE)
29
+ return "context_window";
30
+ if (error.errorCategory === "context_window" && error.message === INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON) {
31
+ return "context_window";
32
+ }
33
+ return undefined;
34
+ }