larkin 0.5.16 → 0.5.18

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.
@@ -190,7 +190,7 @@ export function classifyLarkCliCommand(argv) {
190
190
  return { kind: "passthrough" };
191
191
  const protectedPaths = protectedOperations(argv);
192
192
  if (command === "larkin-draft")
193
- return { kind: "denied", reason: "larkin-draft 已移除;freshness conflict 后请重新判断并执行普通写命令" };
193
+ return { kind: "denied", reason: "larkin-draft 已移除;请使用受 Runtime 身份与目标绑定保护的普通写命令" };
194
194
  if (exactPath(parsed.commandArgv, ["im", "+messages-send"]))
195
195
  return uniqueProtectedOperation(protectedPaths, "send")
196
196
  ? (parsed.flags.has("--thread-id")
@@ -223,21 +223,21 @@ export function classifyLarkCliCommand(argv) {
223
223
  if (exactPath(parsed.commandArgv, ["im", "messages", "create"]) || exactPath(parsed.commandArgv, ["im", "messages", "reply"])) {
224
224
  const expected = parsed.commandArgv[2] === "create" ? "raw-create" : "raw-reply";
225
225
  return uniqueProtectedOperation(protectedPaths, expected)
226
- ? { kind: "denied", reason: "该原始 IM 写入口会旁路 target freshness;请使用 +messages-send/+messages-reply" }
226
+ ? { kind: "denied", reason: "该原始 IM 写入口会旁路 Runtime target binding;请使用 +messages-send/+messages-reply" }
227
227
  : noncanonicalProtectedDecision();
228
228
  }
229
229
  if (["forward", "merge_forward", "urgent_phone", "urgent_sms"]
230
230
  .some((operation) => exactPath(parsed.commandArgv, ["im", "messages", operation]))) {
231
231
  const expected = `raw-${parsed.commandArgv[2]}`;
232
232
  return uniqueProtectedOperation(protectedPaths, expected)
233
- ? { kind: "denied", reason: "该 IM 写入口无法建立 target freshness;请先用 larkin inbox poll 读取目标,再使用受保护的 +messages-send/+messages-reply" }
233
+ ? { kind: "denied", reason: "该 IM 写入口无法建立 Runtime target binding;请先用 larkin inbox poll 读取目标,再使用受保护的 +messages-send/+messages-reply" }
234
234
  : noncanonicalProtectedDecision();
235
235
  }
236
236
  if (["forward", "merge_forward"]
237
237
  .some((operation) => exactPath(parsed.commandArgv, ["im", "threads", operation]))) {
238
238
  const expected = `thread-${parsed.commandArgv[2]}`;
239
239
  return uniqueProtectedOperation(protectedPaths, expected)
240
- ? { kind: "denied", reason: "该 IM forwarding 入口无法建立 target freshness;请先用 larkin inbox poll 读取目标,再使用受保护的 +messages-send/+messages-reply" }
240
+ ? { kind: "denied", reason: "该 IM forwarding 入口无法建立 Runtime target binding;请先用 larkin inbox poll 读取目标,再使用受保护的 +messages-send/+messages-reply" }
241
241
  : noncanonicalProtectedDecision();
242
242
  }
243
243
  if (command === "api")
@@ -347,8 +347,7 @@ function runCommentReply(argv, privateEnv, io, dependencies, store, agentId) {
347
347
  const target = targetKey ? parseDocumentCommentTarget(targetKey) : null;
348
348
  if (!target)
349
349
  throw new Error("comment reply 无法从当前 Agent Inbox 绑定文档评论 locator;先 poll 该消息且不得跨 Agent/评论回复");
350
- if (!store.inboxTargetIsFresh(targetKey))
351
- throw new Error("comment reply 需要先 poll 当前 document-comment target 的最新 Inbox 消息");
350
+ const staleCommentTarget = !store.inboxTargetIsFresh(targetKey);
352
351
  const digest = createHash("sha256").update(input.text).digest("hex");
353
352
  const ledgerKey = `${input.messageId}::${digest}`;
354
353
  const claim = store.mutateJson("freshnessState", { version: 1, cursors: {} }, (state) => {
@@ -421,6 +420,13 @@ function runCommentReply(argv, privateEnv, io, dependencies, store, agentId) {
421
420
  });
422
421
  }
423
422
  const committedWrite = !result.error && result.status === 0;
423
+ if (committedWrite && staleCommentTarget) {
424
+ io.stderr(`${JSON.stringify({
425
+ larkin_notice: "freshness",
426
+ target: targetKey,
427
+ hint: "Newer Inbox messages exist. Re-read the document comment context with larkin before sending a follow-up. Do not resend the same content.",
428
+ })}\n`);
429
+ }
424
430
  const currentReminder = committedWrite
425
431
  ? store.resolveCurrentReminders().filter((reminder) => reminder.deliveryTarget === targetKey).at(-1) ?? null
426
432
  : null;
@@ -464,9 +470,9 @@ function definitiveProviderRejection(result) {
464
470
  }
465
471
  return false;
466
472
  }
467
- function callNative(argv, env, io, dependencies) {
473
+ function callNative(argv, env, io, dependencies, timeoutMs) {
468
474
  const native = dependencies.nativeCommand;
469
- const result = (dependencies.spawn ?? spawnSync)(native?.command ?? resolveOfficialLarkCli({ spawn: dependencies.spawn, env }).command, [...(native?.argsPrefix ?? []), ...argv], { encoding: "utf8", env: { ...process.env, ...env } });
475
+ const result = (dependencies.spawn ?? spawnSync)(native?.command ?? resolveOfficialLarkCli({ spawn: dependencies.spawn, env }).command, [...(native?.argsPrefix ?? []), ...argv], { encoding: "utf8", env: { ...process.env, ...env }, ...(timeoutMs ? { timeout: timeoutMs } : {}) });
470
476
  return result;
471
477
  }
472
478
  function spawnNative(argv, env, io, dependencies) {
@@ -524,7 +530,7 @@ function guardedTarget(decision, argv, store) {
524
530
  const chatId = policyFlagValue(argv, "--chat-id");
525
531
  const userId = policyFlagValue(argv, "--user-id");
526
532
  if (!chatId || userId) {
527
- throw new Error("Runtime +messages-send 必须只使用 Inbox 已确认的 --chat-id;--user-id 无法建立 freshness target");
533
+ throw new Error("Runtime +messages-send 必须使用已确认的 --chat-id;--user-id 无法建立受限 target binding");
528
534
  }
529
535
  return feishuImTarget(`chat:${chatId}`);
530
536
  }
@@ -533,7 +539,7 @@ function guardedTarget(decision, argv, store) {
533
539
  throw new Error(`${decision.operation} 写入缺少 --message-id`);
534
540
  const target = store.resolveInboxMessageTarget(messageId);
535
541
  if (!target)
536
- throw new Error(`无法从 Inbox 状态确定 ${messageId} 的 target;先 poll 对应消息,禁止旁路 freshness`);
542
+ throw new Error(`无法从 Inbox 状态确定 ${messageId} 的 target;先 poll 对应消息,禁止旁路 target binding`);
537
543
  return feishuImTarget(target);
538
544
  }
539
545
  function recallLedgerEntry(store, messageId) {
@@ -889,22 +895,62 @@ function intentId(target, argv) {
889
895
  .update(JSON.stringify([target, argv])).digest("hex");
890
896
  return `larkin-${fingerprint.slice(0, 32)}`;
891
897
  }
892
- function emitFreshnessError(io, input) {
898
+ function emitFreshnessNotice(io, input) {
893
899
  io.stderr(`${JSON.stringify({
894
- ok: false,
895
- identity: "bot",
896
- error: { type: input.subtype === "freshness_conflict" ? "conflict" : "unavailable", subtype: input.subtype,
897
- ...(input.reason ? { message: input.reason } : {}) },
900
+ larkin_notice: "freshness",
898
901
  target: input.target,
899
- ...(input.current ? { current_cursor: input.current } : {}),
900
- ...(input.messages ? { unseen_messages: input.messages } : {}),
901
- next: "Reconsider the returned context, then retry the ordinary send/reply/card/recall command; history is probed again before every write.",
902
+ newer_messages: input.messages.length,
903
+ latest_message_ids: input.messages.map((message) => message.message_id),
904
+ hint: "Newer messages exist. Re-read this target with larkin and send a short follow-up only if they change your answer. Do not resend the same content.",
902
905
  })}\n`);
903
906
  }
904
907
  function freshnessGeneration(env) {
905
908
  return typeof env.LARKIN_RUNTIME_OBSERVATION_GENERATION === "string" && env.LARKIN_RUNTIME_OBSERVATION_GENERATION
906
909
  ? env.LARKIN_RUNTIME_OBSERVATION_GENERATION : "external";
907
910
  }
911
+ // The post-write observation is advisory: cap it tightly so success is not
912
+ // held hostage by a slow provider history response.
913
+ const FRESHNESS_PROBE_TIMEOUT_MS = 500;
914
+ function emitSoftFreshnessNotice(target, targetKey, seen, ownMessageId, env, io, dependencies, store) {
915
+ let timedOut = false;
916
+ try {
917
+ const gated = evaluateFreshness({
918
+ seen,
919
+ adapter: feishuImFreshnessAdapter,
920
+ probe: () => {
921
+ const result = callNative(probeArgv(target), env, io, dependencies, FRESHNESS_PROBE_TIMEOUT_MS);
922
+ const error = result.error;
923
+ timedOut = error?.code === "ETIMEDOUT"
924
+ || error?.message.includes("ETIMEDOUT") === true
925
+ || (result.status === null && result.signal === "SIGTERM");
926
+ return parseHistory(result, target, true);
927
+ },
928
+ });
929
+ if (gated.status === "unavailable") {
930
+ if (timedOut)
931
+ io.stderr(`lark-cli: freshness observation timed out after ${FRESHNESS_PROBE_TIMEOUT_MS}ms; no read position was recorded\n`);
932
+ return;
933
+ }
934
+ if (!seen && gated.status === "fresh" && gated.current) {
935
+ // A first authoritative observation establishes a quiet baseline from
936
+ // the provider's latest 20-message window, including messages that
937
+ // arrived while this write was in flight. This does not claim the Agent
938
+ // read those messages; it only enables change detection on later writes.
939
+ store.mergeFreshnessCursor(targetKey, gated.current, mergeFeishuImCursor, freshnessGeneration(env));
940
+ }
941
+ if (gated.status !== "conflict")
942
+ return;
943
+ store.mergeFreshnessCursor(targetKey, gated.current, mergeFeishuImCursor, freshnessGeneration(env));
944
+ const newer = gated.context.filter((message) => message.message_id !== ownMessageId);
945
+ if (newer.length)
946
+ emitFreshnessNotice(io, { target: targetKey, messages: newer });
947
+ }
948
+ catch {
949
+ // freshness 仅作提示:观察失败绝不能改变已完成写入的结果。
950
+ if (timedOut)
951
+ io.stderr(`lark-cli: freshness observation timed out after ${FRESHNESS_PROBE_TIMEOUT_MS}ms; no read position was recorded\n`);
952
+ }
953
+ }
908
954
  function rawFlagValue(argv, flag) {
909
955
  const nativeArgv = nativeArgvBeforeBoundary(argv);
910
956
  const direct = nativeArgv.indexOf(flag);
@@ -1066,23 +1112,6 @@ function writeResponseMessage(result) {
1066
1112
  return null;
1067
1113
  }
1068
1114
  }
1069
- function observeSuccessfulWrite(result, target, targetKey, store, generation) {
1070
- if (result.error || result.status !== 0)
1071
- return false;
1072
- try {
1073
- const candidate = writeResponseMessage(result);
1074
- if (!candidate)
1075
- return false;
1076
- const snapshot = parseHistory({ ...result, stdout: JSON.stringify({ ok: true, data: { messages: [candidate] } }) }, target);
1077
- const cursor = feishuImFreshnessAdapter.cursor(snapshot);
1078
- if (cursor)
1079
- store.mergeFreshnessCursor(targetKey, cursor, mergeFeishuImCursor, generation);
1080
- return cursor !== null;
1081
- }
1082
- catch {
1083
- return false;
1084
- }
1085
- }
1086
1115
  function emitNativeResult(result, io) {
1087
1116
  if (result.stdout)
1088
1117
  io.stdout(result.stdout);
@@ -1125,35 +1154,6 @@ function emitDuplicatedRecall(io, messageId, target) {
1125
1154
  })}\n`);
1126
1155
  return 0;
1127
1156
  }
1128
- function emitCommittedUnverified(result, io, input) {
1129
- let providerResponse;
1130
- try {
1131
- providerResponse = JSON.parse(result.stdout || "");
1132
- }
1133
- catch {
1134
- providerResponse = { raw_stdout: result.stdout || "" };
1135
- }
1136
- const providerDocument = providerResponse && typeof providerResponse === "object" && !Array.isArray(providerResponse)
1137
- ? providerResponse
1138
- : { provider_response: providerResponse };
1139
- io.stdout(`${JSON.stringify({
1140
- ...providerDocument,
1141
- ok: true,
1142
- committed: true,
1143
- verified: false,
1144
- cursor_advanced: false,
1145
- target: input.target,
1146
- verification: {
1147
- status: "unverified",
1148
- subtype: "post_write_unverified",
1149
- message: input.reason,
1150
- ...(input.current ? { current_cursor: input.current } : {}),
1151
- ...(input.messages ? { unseen_messages: input.messages } : {}),
1152
- },
1153
- ...(result.stderr ? { provider_stderr_present: true } : {}),
1154
- })}\n`);
1155
- return 0;
1156
- }
1157
1157
  export function runLarkCli(argv, env = process.env, dependencies = {}) {
1158
1158
  const io = dependencies.io ?? defaultIo();
1159
1159
  let effectiveArgv = argv;
@@ -1235,20 +1235,6 @@ export function runLarkCli(argv, env = process.env, dependencies = {}) {
1235
1235
  const targetKey = serializeFeishuImTarget(target);
1236
1236
  const generation = freshnessGeneration(privateEnv);
1237
1237
  const seen = store.readFreshnessCursor(targetKey, generation);
1238
- const gated = evaluateFreshness({
1239
- seen,
1240
- adapter: feishuImFreshnessAdapter,
1241
- probe: () => parseHistory(callNative(probeArgv(target), privateEnv, io, nativeDependencies), target, true),
1242
- });
1243
- if (gated.status === "unavailable") {
1244
- emitFreshnessError(io, { subtype: "freshness_unavailable", target: targetKey, reason: gated.reason });
1245
- return 3;
1246
- }
1247
- if (gated.status === "conflict") {
1248
- emitFreshnessError(io, { subtype: "freshness_conflict", target: targetKey, current: gated.current, messages: gated.context });
1249
- store.mergeFreshnessCursor(targetKey, gated.current, mergeFeishuImCursor, generation);
1250
- return 3;
1251
- }
1252
1238
  if (decision.operation === "recall") {
1253
1239
  const claim = claimRecall(store, recallMessageId, targetKey);
1254
1240
  if (claim === "deleted")
@@ -1258,16 +1244,15 @@ export function runLarkCli(argv, env = process.env, dependencies = {}) {
1258
1244
  }
1259
1245
  const recallWrite = callNative(botArgv(effectiveArgv, "", decision), privateEnv, io, nativeDependencies);
1260
1246
  if (!recallWrite.error && recallWrite.status === 0) {
1261
- const predictedSnapshot = {
1262
- messages: gated.snapshot.messages.filter((message) => message.message_id !== recallMessageId),
1263
- };
1264
- const predictedCursor = feishuImFreshnessAdapter.cursor(predictedSnapshot);
1265
- finalizeSuccessfulRecall(store, recallMessageId, targetKey, predictedCursor, generation);
1247
+ finalizeSuccessfulRecall(store, recallMessageId, targetKey, null, generation);
1266
1248
  }
1267
1249
  else if (definitiveProviderRejection(recallWrite)) {
1268
1250
  finalizeFailedRecall(store, recallMessageId, targetKey);
1269
1251
  }
1270
- return emitNativeResult(recallWrite, io);
1252
+ const code = emitNativeResult(recallWrite, io);
1253
+ if (code === 0)
1254
+ emitSoftFreshnessNotice(target, targetKey, seen, undefined, privateEnv, io, nativeDependencies, store);
1255
+ return code;
1271
1256
  }
1272
1257
  if (decision.operation === "urgent-app") {
1273
1258
  const userIds = assertUrgentAppPreconditions(effectiveArgv, urgent.message, target, agent.feishuAppId);
@@ -1291,7 +1276,10 @@ export function runLarkCli(argv, env = process.env, dependencies = {}) {
1291
1276
  })}\n`);
1292
1277
  return 2;
1293
1278
  }
1294
- return emitNativeResult(write, io);
1279
+ const code = emitNativeResult(write, io);
1280
+ if (code === 0)
1281
+ emitSoftFreshnessNotice(target, targetKey, seen, writeResponseMessage(write)?.message_id, privateEnv, io, nativeDependencies, store);
1282
+ return code;
1295
1283
  }
1296
1284
  const writeMessage = writeResponseMessage(write);
1297
1285
  const deliveryTarget = decision.operation === "send"
@@ -1331,40 +1319,10 @@ export function runLarkCli(argv, env = process.env, dependencies = {}) {
1331
1319
  io.stderr(`lark-cli: reminder delivery audit failed: ${error instanceof Error ? error.message : String(error)}\n`);
1332
1320
  }
1333
1321
  const duplicate = memo.duplicate;
1334
- if (duplicate) {
1335
- observeSuccessfulWrite(write, target, targetKey, store, generation);
1336
- emitDuplicatedWrite(write, io, { target: targetKey });
1337
- return 0;
1338
- }
1339
- if (!write.error && write.status === 0 && !observeSuccessfulWrite(write, target, targetKey, store, generation)) {
1340
- const responseMessage = writeResponseMessage(write);
1341
- try {
1342
- const postSnapshot = parseHistory(callNative(probeArgv(target), privateEnv, io, nativeDependencies), target, true);
1343
- const postCursor = feishuImFreshnessAdapter.cursor(postSnapshot);
1344
- const unseenAfterWrite = feishuImFreshnessAdapter.unseen(gated.current, postSnapshot);
1345
- const confirmedOwnWrite = responseMessage && postCursor
1346
- && unseenAfterWrite.some((message) => message.message_id === responseMessage.message_id)
1347
- && unseenAfterWrite.every((message) => message.message_id === responseMessage.message_id);
1348
- if (!confirmedOwnWrite) {
1349
- return emitCommittedUnverified(write, io, {
1350
- target: targetKey,
1351
- ...(postCursor ? { current: postCursor } : {}),
1352
- messages: unseenAfterWrite,
1353
- reason: responseMessage
1354
- ? "provider write succeeded but bounded post-write probe found an additional concurrent update; cursor was not advanced"
1355
- : "provider write succeeded without a message id/revision and bounded post-write probe could not identify the write; cursor was not advanced",
1356
- });
1357
- }
1358
- store.mergeFreshnessCursor(targetKey, postCursor, mergeFeishuImCursor, generation);
1359
- }
1360
- catch {
1361
- return emitCommittedUnverified(write, io, {
1362
- target: targetKey,
1363
- reason: "provider write succeeded but bounded post-write confirmation was unavailable; cursor was not advanced",
1364
- });
1365
- }
1366
- }
1367
- return emitNativeResult(write, io);
1322
+ const code = duplicate ? emitDuplicatedWrite(write, io, { target: targetKey }) : emitNativeResult(write, io);
1323
+ if (code === 0)
1324
+ emitSoftFreshnessNotice(target, targetKey, seen, writeMessage?.message_id, privateEnv, io, nativeDependencies, store);
1325
+ return code;
1368
1326
  }
1369
1327
  catch (error) {
1370
1328
  io.stderr(`lark-cli: ${error instanceof Error ? error.message : String(error)}\n`);
package/dist/app/lark.mjs CHANGED
@@ -4,6 +4,20 @@ import { spawn } from "node:child_process";
4
4
  import * as larkinConfig from "../platform/config.mjs";
5
5
  import { assessPassthrough, PASSTHROUGH_USAGE } from "../feishu/lark-passthrough.mjs";
6
6
  import { managedOfficialLarkCli } from "./agent-lark-cli-workspace.mjs";
7
+ import { isLarkinRuntimeContext } from "./runtime-context.mjs";
8
+ const runtimeScopedCommands = new Set(["inbox", "reminder", "interaction", "profile", "comment"]);
9
+ const requestedCommand = process.argv.slice(2).find((argument) => !argument.startsWith("-"));
10
+ const runtimeContext = isLarkinRuntimeContext(process.env);
11
+ if (!process.env.LARKIN_AGENT_ID && runtimeContext) {
12
+ console.error(`larkin: Runtime Agent authority is missing for "${requestedCommand ?? "lark-cli"}" (LARKIN_AGENT_ID is required). `
13
+ + "Runtime commands cannot fall back to the active Agent.");
14
+ process.exit(2);
15
+ }
16
+ if (!process.env.LARKIN_AGENT_ID && requestedCommand && runtimeScopedCommands.has(requestedCommand)) {
17
+ console.error(`larkin: Runtime Agent authority is missing for "${requestedCommand}" (LARKIN_AGENT_ID is required). `
18
+ + "Agent-scoped commands cannot fall back to the active Agent.");
19
+ process.exit(2);
20
+ }
7
21
  const decision = assessPassthrough(process.argv.slice(2), process.env);
8
22
  if (!decision.ok) {
9
23
  console.error(`larkin: ${decision.reason}`);
@@ -240,18 +240,29 @@ function runOfficialLarkCliAsync(command, args, env, dependencies) {
240
240
  });
241
241
  });
242
242
  }
243
- function expectedRuntimeCommandShim() {
243
+ function expectedRuntimeCommandShim(agent) {
244
244
  const standalone = process.env.LARKIN_STANDALONE === "1";
245
245
  const binaryEntry = fileURLToPath(new URL("./binary-entry.mjs", import.meta.url));
246
246
  const argumentsPrefix = standalone ? [] : [binaryEntry];
247
247
  const command = [process.execPath, ...argumentsPrefix].map(shellQuote).join(" ");
248
- return `#!/bin/sh\nexec ${command} "$@"\n`;
248
+ const stateDir = path.resolve(agent.stateDir);
249
+ const configDir = path.resolve(stateDir, "../../..");
250
+ return [
251
+ "#!/bin/sh",
252
+ "export LARKIN_RUNTIME=1",
253
+ `export LARKIN_AGENT_ID=${shellQuote(agent.agentId)}`,
254
+ `export LARKIN_STATE_DIR=${shellQuote(stateDir)}`,
255
+ `export LARKIN_CONFIG_DIR=${shellQuote(configDir)}`,
256
+ `export LARKIN_HOME=${shellQuote(configDir)}`,
257
+ `exec ${command} "$@"`,
258
+ "",
259
+ ].join("\n");
249
260
  }
250
261
  export function installRuntimeCommandShims(agent) {
251
262
  const stateDir = path.resolve(agent.stateDir);
252
263
  const commandDir = path.join(stateDir, "runtime-bin");
253
264
  assertSecureRuntimeCommandDirectory(commandDir);
254
- const fileContents = expectedRuntimeCommandShim();
265
+ const fileContents = expectedRuntimeCommandShim(agent);
255
266
  for (const name of ["larkin"]) {
256
267
  const file = path.join(commandDir, name);
257
268
  const temporary = path.join(commandDir, `.${name}.${process.pid}.${crypto.randomUUID()}.tmp`);
@@ -274,7 +285,7 @@ function validateRuntimeCommandShims(agent) {
274
285
  || (typeof process.getuid === "function" && stat.uid !== process.getuid())
275
286
  || (!exactMode(stat, 0o700) && !exactMode(stat, 0o500)))
276
287
  throw new Error("Runtime command shim 不安全");
277
- if (fs.readFileSync(file, "utf8") !== expectedRuntimeCommandShim()) {
288
+ if (fs.readFileSync(file, "utf8") !== expectedRuntimeCommandShim(agent)) {
278
289
  throw new Error("Runtime command shim 内容无效");
279
290
  }
280
291
  }
@@ -0,0 +1,17 @@
1
+ function hasRuntimeBin(pathValue) {
2
+ if (!pathValue)
3
+ return false;
4
+ return pathValue.split(process.platform === "win32" ? ";" : ":")
5
+ .some((entry) => /(?:^|[\\/])runtime-bin$/.test(entry));
6
+ }
7
+ /**
8
+ * Runtime markers are deliberately independent of LARKIN_AGENT_ID. They keep a
9
+ * damaged Runtime environment from falling through to a terminal's activeAgent
10
+ * selection when its per-Agent identity has been stripped.
11
+ */
12
+ export function isLarkinRuntimeContext(env = process.env) {
13
+ return env.LARKIN_RUNTIME === "1"
14
+ || Boolean(env.LARKIN_RUNTIME_OBSERVATION_GENERATION)
15
+ || Boolean(env.LARKIN_STATE_DIR)
16
+ || hasRuntimeBin(env.PATH);
17
+ }
@@ -402,11 +402,17 @@ function dashboardMutation(value) {
402
402
  "set-chat-mention": ["operation", "agentId", "chatId", "value"], "set-agent-runtime": ["operation", "agentId", "runtime", "model"],
403
403
  "set-agent-model": ["operation", "agentId", "model"], "set-agent-effort": ["operation", "agentId", "effort"],
404
404
  "set-global-inbox-audit": ["operation", "enabled", "intervalMs"], "set-agent-inbox-audit": ["operation", "agentId", "enabled", "intervalMs"],
405
+ "set-global-processing-eye": ["operation", "enabled"], "set-agent-processing-eye": ["operation", "agentId", "enabled"],
405
406
  };
406
407
  if (!allowed[operation] || Object.keys(value).some((key) => !allowed[operation].includes(key)))
407
408
  throw new Error("unsupported operation");
408
409
  if (operation === "set-global-mention")
409
410
  return { kind: operation, value: String(value.value) };
411
+ if (operation === "set-global-processing-eye") {
412
+ if (typeof value.enabled !== "boolean")
413
+ throw new Error("invalid processing-eye enabled");
414
+ return { kind: operation, enabled: value.enabled };
415
+ }
410
416
  if (operation === "set-global-inbox-audit") {
411
417
  if (value.enabled !== undefined && typeof value.enabled !== "boolean")
412
418
  throw new Error("invalid inbox-audit enabled");
@@ -422,6 +428,11 @@ function dashboardMutation(value) {
422
428
  throw new Error("invalid inbox-audit interval");
423
429
  return { kind: operation, agentId, ...(value.enabled !== undefined ? { enabled: value.enabled } : {}), ...(value.intervalMs !== undefined ? { intervalMs: value.intervalMs } : {}) };
424
430
  }
431
+ if (operation === "set-agent-processing-eye") {
432
+ if (value.enabled !== "inherit" && typeof value.enabled !== "boolean")
433
+ throw new Error("invalid processing-eye enabled");
434
+ return { kind: operation, agentId, enabled: value.enabled };
435
+ }
425
436
  if (operation === "set-agent-mention")
426
437
  return { kind: operation, agentId, value: String(value.value) };
427
438
  if (operation === "set-chat-mention")