@sideboard-ai/core 0.1.309 → 0.1.311

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/dist/index.cjs CHANGED
@@ -6720,6 +6720,191 @@ var init_message_parts = __esm({
6720
6720
  }
6721
6721
  });
6722
6722
 
6723
+ // src/plan/ask-user.ts
6724
+ function planQuestionsSignature(questions2) {
6725
+ return questions2.map((q) => {
6726
+ const opts = q.options.map((o) => o.label).join("");
6727
+ return `${q.question}${opts}`;
6728
+ }).join("");
6729
+ }
6730
+ function asRecord2(v) {
6731
+ return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
6732
+ }
6733
+ function parseOptions(raw) {
6734
+ if (!Array.isArray(raw)) return [];
6735
+ const out = [];
6736
+ for (const item of raw) {
6737
+ if (typeof item === "string" && item.trim()) {
6738
+ out.push({ label: item.trim() });
6739
+ continue;
6740
+ }
6741
+ const o = asRecord2(item);
6742
+ if (!o) continue;
6743
+ const label = typeof o.label === "string" ? o.label.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.title === "string" ? o.title.trim() : "";
6744
+ if (!label) continue;
6745
+ const description = typeof o.description === "string" && o.description.trim() ? o.description.trim() : void 0;
6746
+ out.push({ label, description });
6747
+ }
6748
+ return out;
6749
+ }
6750
+ function parseOneQuestion(raw) {
6751
+ const o = asRecord2(raw);
6752
+ if (!o) return null;
6753
+ const question = typeof o.question === "string" ? o.question.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.prompt === "string" ? o.prompt.trim() : "";
6754
+ if (!question) return null;
6755
+ const options = parseOptions(o.options);
6756
+ if (options.length < 1) return null;
6757
+ const header = typeof o.header === "string" && o.header.trim() ? o.header.trim() : void 0;
6758
+ const multiSelect = Boolean(o.multiSelect ?? o.multi_select);
6759
+ return { question, header, multiSelect, options };
6760
+ }
6761
+ function parsePlanQuestionsInput(input) {
6762
+ const root = asRecord2(input);
6763
+ if (!root) return [];
6764
+ const list = Array.isArray(root.questions) ? root.questions : Array.isArray(root.question) ? root.question : root.question || root.prompt ? [root] : [];
6765
+ const out = [];
6766
+ for (const item of list) {
6767
+ const q = parseOneQuestion(item);
6768
+ if (q) out.push(q);
6769
+ }
6770
+ return out;
6771
+ }
6772
+ function isAskUserToolName(name) {
6773
+ if (!name) return false;
6774
+ const base = name.replace(/^mcp__sideboard__/i, "");
6775
+ return ASK_USER_TOOL_RE.test(name) || /^ask_user$/i.test(base);
6776
+ }
6777
+ function lastTopLevelToolIsAskUser(parts) {
6778
+ if (!parts?.length) return false;
6779
+ for (let i = parts.length - 1; i >= 0; i--) {
6780
+ const part = parts[i];
6781
+ if (part.type === "tool" && !part.parentId) {
6782
+ return isAskUserToolName(part.name);
6783
+ }
6784
+ }
6785
+ return false;
6786
+ }
6787
+ function hasTopLevelTool(parts) {
6788
+ return Boolean(parts?.some((part) => part.type === "tool" && !part.parentId));
6789
+ }
6790
+ function questionsFromToolPart(part) {
6791
+ const fromInput = parsePlanQuestionsInput(part.input);
6792
+ if (fromInput.length) return fromInput;
6793
+ const raw = part.result?.trim();
6794
+ if (!raw || !raw.startsWith("{")) return [];
6795
+ try {
6796
+ return parsePlanQuestionsInput(JSON.parse(raw));
6797
+ } catch {
6798
+ return [];
6799
+ }
6800
+ }
6801
+ function extractPendingPlanQuestions(parts) {
6802
+ if (!parts?.length) return null;
6803
+ for (let i = parts.length - 1; i >= 0; i--) {
6804
+ const p = parts[i];
6805
+ if (p.type !== "tool" || !isAskUserToolName(p.name)) continue;
6806
+ const questions2 = questionsFromToolPart(p);
6807
+ if (!questions2.length) continue;
6808
+ return {
6809
+ id: p.id || `ask-${i}`,
6810
+ signature: planQuestionsSignature(questions2),
6811
+ questions: questions2,
6812
+ source: p.name || "ask_user"
6813
+ };
6814
+ }
6815
+ return null;
6816
+ }
6817
+ function pendingFromAskBlock(block, dismissedQuestionSignature) {
6818
+ if (!block || block.source !== "ask_user" || !block.questions?.length) return null;
6819
+ const dismissed = dismissedQuestionSignature?.trim();
6820
+ if (dismissed && (!block.questionSignature || block.questionSignature === dismissed)) {
6821
+ return null;
6822
+ }
6823
+ const signature = block.questionSignature?.trim() || planQuestionsSignature(block.questions);
6824
+ return {
6825
+ id: `ask-block:${signature}`,
6826
+ signature,
6827
+ questions: block.questions,
6828
+ source: "ask_user"
6829
+ };
6830
+ }
6831
+ function askBlockStillWaiting(block, messages) {
6832
+ if (!messages?.length) return Boolean(block?.at);
6833
+ const askedAt = block?.at;
6834
+ if (!askedAt) return false;
6835
+ for (let i = messages.length - 1; i >= 0; i--) {
6836
+ const message = messages[i];
6837
+ if (message.role !== "user" || message.origin === "continue") continue;
6838
+ if (!message.ts) return true;
6839
+ return askedAt > message.ts;
6840
+ }
6841
+ return true;
6842
+ }
6843
+ function latestPendingPlanQuestions(input) {
6844
+ if (input.userReplied) return null;
6845
+ const fromBlock = () => pendingFromAskBlock(input.agentBlock, input.dismissedQuestionSignature);
6846
+ if (lastTopLevelToolIsAskUser(input.liveParts)) {
6847
+ return extractPendingPlanQuestions(input.liveParts) ?? fromBlock();
6848
+ }
6849
+ const last = input.messages[input.messages.length - 1];
6850
+ if (last?.role === "agent" && lastTopLevelToolIsAskUser(last.parts)) {
6851
+ return extractPendingPlanQuestions(last.parts) ?? fromBlock();
6852
+ }
6853
+ if (hasTopLevelTool(input.liveParts) || last?.role === "agent" && hasTopLevelTool(last.parts)) {
6854
+ return null;
6855
+ }
6856
+ if (input.status === "running" || input.status === "queued") return null;
6857
+ const stamped = fromBlock();
6858
+ if (stamped && askBlockStillWaiting(input.agentBlock, input.messages)) {
6859
+ return stamped;
6860
+ }
6861
+ return null;
6862
+ }
6863
+ function isPlanQuestionAnswersMessage(text7) {
6864
+ return text7.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
6865
+ }
6866
+ function formatPlanQuestionAnswers(questions2, answers) {
6867
+ const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
6868
+ for (let i = 0; i < questions2.length; i++) {
6869
+ const q = questions2[i];
6870
+ const a = answers.find((x) => x.questionIndex === i);
6871
+ const header = q.header ? `**${q.header}** \u2014 ` : "";
6872
+ const parts = [];
6873
+ if (a?.selected.length) parts.push(a.selected.join(", "));
6874
+ if (a?.other?.trim()) parts.push(a.other.trim());
6875
+ const body = parts.length ? parts.join(" \xB7 ") : "(no answer)";
6876
+ lines.push(`${i + 1}. ${header}${q.question} `);
6877
+ lines.push(` \u2192 ${body}`);
6878
+ }
6879
+ return lines.join("\n");
6880
+ }
6881
+ function formatPlanQuestionsForChat(questions2) {
6882
+ const lines = ["### Questions for you", ""];
6883
+ for (let i = 0; i < questions2.length; i++) {
6884
+ const q = questions2[i];
6885
+ const header = q.header ? `**${q.header}** \u2014 ` : "";
6886
+ lines.push(`${i + 1}. ${header}${q.question}`);
6887
+ for (let oi = 0; oi < q.options.length; oi++) {
6888
+ const opt = q.options[oi];
6889
+ const desc = opt.description?.trim();
6890
+ lines.push(
6891
+ desc ? ` - **${opt.label}** \u2014 ${desc}` : ` - **${opt.label}**`
6892
+ );
6893
+ }
6894
+ lines.push("");
6895
+ }
6896
+ lines.push("_Answer in the composer below._");
6897
+ return lines.join("\n").trimEnd();
6898
+ }
6899
+ var ASK_USER_TOOL_RE, PLAN_QUESTION_ANSWERS_PREFIX;
6900
+ var init_ask_user = __esm({
6901
+ "src/plan/ask-user.ts"() {
6902
+ "use strict";
6903
+ ASK_USER_TOOL_RE = /^(AskUserQuestion|ask_user|mcp__sideboard__ask_user)$/i;
6904
+ PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
6905
+ }
6906
+ });
6907
+
6723
6908
  // src/board/agent-block-view.ts
6724
6909
  function worktreeBoardStatus(group) {
6725
6910
  const order = ["running", "queued", "error", "broken"];
@@ -6742,6 +6927,11 @@ function visibleAgentBlock(thread) {
6742
6927
  const block = thread.agentBlock;
6743
6928
  if (!block?.reason?.trim()) return null;
6744
6929
  if (isDismissedAskUserBlock(block, thread.dismissedQuestionSignature)) return null;
6930
+ if (block.source === "ask_user") {
6931
+ const inFlight = thread.status === "running" || thread.status === "queued";
6932
+ if (inFlight && !block.onScreen) return null;
6933
+ if (!askBlockStillWaiting(block, thread.messages)) return null;
6934
+ }
6745
6935
  return block;
6746
6936
  }
6747
6937
  function worktreeAgentBlock(group) {
@@ -6777,6 +6967,7 @@ function threadCardBlockFields(group) {
6777
6967
  var init_agent_block_view = __esm({
6778
6968
  "src/board/agent-block-view.ts"() {
6779
6969
  "use strict";
6970
+ init_ask_user();
6780
6971
  }
6781
6972
  });
6782
6973
 
@@ -8309,175 +8500,6 @@ var init_injected_notices = __esm({
8309
8500
  }
8310
8501
  });
8311
8502
 
8312
- // src/plan/ask-user.ts
8313
- function planQuestionsSignature(questions2) {
8314
- return questions2.map((q) => {
8315
- const opts = q.options.map((o) => o.label).join("");
8316
- return `${q.question}${opts}`;
8317
- }).join("");
8318
- }
8319
- function asRecord2(v) {
8320
- return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
8321
- }
8322
- function parseOptions(raw) {
8323
- if (!Array.isArray(raw)) return [];
8324
- const out = [];
8325
- for (const item of raw) {
8326
- if (typeof item === "string" && item.trim()) {
8327
- out.push({ label: item.trim() });
8328
- continue;
8329
- }
8330
- const o = asRecord2(item);
8331
- if (!o) continue;
8332
- const label = typeof o.label === "string" ? o.label.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.title === "string" ? o.title.trim() : "";
8333
- if (!label) continue;
8334
- const description = typeof o.description === "string" && o.description.trim() ? o.description.trim() : void 0;
8335
- out.push({ label, description });
8336
- }
8337
- return out;
8338
- }
8339
- function parseOneQuestion(raw) {
8340
- const o = asRecord2(raw);
8341
- if (!o) return null;
8342
- const question = typeof o.question === "string" ? o.question.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.prompt === "string" ? o.prompt.trim() : "";
8343
- if (!question) return null;
8344
- const options = parseOptions(o.options);
8345
- if (options.length < 1) return null;
8346
- const header = typeof o.header === "string" && o.header.trim() ? o.header.trim() : void 0;
8347
- const multiSelect = Boolean(o.multiSelect ?? o.multi_select);
8348
- return { question, header, multiSelect, options };
8349
- }
8350
- function parsePlanQuestionsInput(input) {
8351
- const root = asRecord2(input);
8352
- if (!root) return [];
8353
- const list = Array.isArray(root.questions) ? root.questions : Array.isArray(root.question) ? root.question : root.question || root.prompt ? [root] : [];
8354
- const out = [];
8355
- for (const item of list) {
8356
- const q = parseOneQuestion(item);
8357
- if (q) out.push(q);
8358
- }
8359
- return out;
8360
- }
8361
- function isAskUserToolName(name) {
8362
- if (!name) return false;
8363
- const base = name.replace(/^mcp__sideboard__/i, "");
8364
- return ASK_USER_TOOL_RE.test(name) || /^ask_user$/i.test(base);
8365
- }
8366
- function questionsFromToolPart(part) {
8367
- const fromInput = parsePlanQuestionsInput(part.input);
8368
- if (fromInput.length) return fromInput;
8369
- const raw = part.result?.trim();
8370
- if (!raw || !raw.startsWith("{")) return [];
8371
- try {
8372
- return parsePlanQuestionsInput(JSON.parse(raw));
8373
- } catch {
8374
- return [];
8375
- }
8376
- }
8377
- function extractPendingPlanQuestions(parts) {
8378
- if (!parts?.length) return null;
8379
- for (let i = parts.length - 1; i >= 0; i--) {
8380
- const p = parts[i];
8381
- if (p.type !== "tool" || !isAskUserToolName(p.name)) continue;
8382
- const questions2 = questionsFromToolPart(p);
8383
- if (!questions2.length) continue;
8384
- return {
8385
- id: p.id || `ask-${i}`,
8386
- signature: planQuestionsSignature(questions2),
8387
- questions: questions2,
8388
- source: p.name || "ask_user"
8389
- };
8390
- }
8391
- return null;
8392
- }
8393
- function pendingFromAskBlock(block, dismissedQuestionSignature) {
8394
- if (!block || block.source !== "ask_user" || !block.questions?.length) return null;
8395
- const dismissed = dismissedQuestionSignature?.trim();
8396
- if (dismissed && (!block.questionSignature || block.questionSignature === dismissed)) {
8397
- return null;
8398
- }
8399
- const signature = block.questionSignature?.trim() || planQuestionsSignature(block.questions);
8400
- return {
8401
- id: `ask-block:${signature}`,
8402
- signature,
8403
- questions: block.questions,
8404
- source: "ask_user"
8405
- };
8406
- }
8407
- function askBlockStillWaiting(block, messages) {
8408
- const askedAt = block?.at;
8409
- if (!askedAt) return false;
8410
- for (let i = messages.length - 1; i >= 0; i--) {
8411
- const message = messages[i];
8412
- if (message.role !== "user" || message.origin === "continue") continue;
8413
- if (!message.ts) return true;
8414
- return askedAt > message.ts;
8415
- }
8416
- return true;
8417
- }
8418
- function latestPendingPlanQuestions(input) {
8419
- if (input.userReplied) return null;
8420
- const fromLive = extractPendingPlanQuestions(input.liveParts);
8421
- if (fromLive) return fromLive;
8422
- const last = input.messages[input.messages.length - 1];
8423
- if (last?.role === "agent") {
8424
- const fromParts = extractPendingPlanQuestions(last.parts);
8425
- if (fromParts) return fromParts;
8426
- }
8427
- const fromBlock = pendingFromAskBlock(
8428
- input.agentBlock,
8429
- input.dismissedQuestionSignature
8430
- );
8431
- if (fromBlock && askBlockStillWaiting(input.agentBlock, input.messages)) {
8432
- return fromBlock;
8433
- }
8434
- return null;
8435
- }
8436
- function isPlanQuestionAnswersMessage(text7) {
8437
- return text7.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
8438
- }
8439
- function formatPlanQuestionAnswers(questions2, answers) {
8440
- const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
8441
- for (let i = 0; i < questions2.length; i++) {
8442
- const q = questions2[i];
8443
- const a = answers.find((x) => x.questionIndex === i);
8444
- const header = q.header ? `**${q.header}** \u2014 ` : "";
8445
- const parts = [];
8446
- if (a?.selected.length) parts.push(a.selected.join(", "));
8447
- if (a?.other?.trim()) parts.push(a.other.trim());
8448
- const body = parts.length ? parts.join(" \xB7 ") : "(no answer)";
8449
- lines.push(`${i + 1}. ${header}${q.question} `);
8450
- lines.push(` \u2192 ${body}`);
8451
- }
8452
- return lines.join("\n");
8453
- }
8454
- function formatPlanQuestionsForChat(questions2) {
8455
- const lines = ["### Questions for you", ""];
8456
- for (let i = 0; i < questions2.length; i++) {
8457
- const q = questions2[i];
8458
- const header = q.header ? `**${q.header}** \u2014 ` : "";
8459
- lines.push(`${i + 1}. ${header}${q.question}`);
8460
- for (let oi = 0; oi < q.options.length; oi++) {
8461
- const opt = q.options[oi];
8462
- const desc = opt.description?.trim();
8463
- lines.push(
8464
- desc ? ` - **${opt.label}** \u2014 ${desc}` : ` - **${opt.label}**`
8465
- );
8466
- }
8467
- lines.push("");
8468
- }
8469
- lines.push("_Answer in the composer below._");
8470
- return lines.join("\n").trimEnd();
8471
- }
8472
- var ASK_USER_TOOL_RE, PLAN_QUESTION_ANSWERS_PREFIX;
8473
- var init_ask_user = __esm({
8474
- "src/plan/ask-user.ts"() {
8475
- "use strict";
8476
- ASK_USER_TOOL_RE = /^(AskUserQuestion|ask_user|mcp__sideboard__ask_user)$/i;
8477
- PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
8478
- }
8479
- });
8480
-
8481
8503
  // src/orchestrator/task-state.ts
8482
8504
  function isTerminalTaskState(state) {
8483
8505
  return state !== "submitted" && state !== "working";
@@ -8489,14 +8511,7 @@ function needsCoordinatorAction(state) {
8489
8511
  return isIncompleteTaskState(state) || state === "input-required";
8490
8512
  }
8491
8513
  function endedOnAskUser(parts) {
8492
- if (!parts?.length) return false;
8493
- for (let i = parts.length - 1; i >= 0; i--) {
8494
- const p = parts[i];
8495
- if (p.type === "tool" && !p.parentId) {
8496
- return isAskUserToolName(p.name);
8497
- }
8498
- }
8499
- return false;
8514
+ return lastTopLevelToolIsAskUser(parts);
8500
8515
  }
8501
8516
  function deriveTaskState(input) {
8502
8517
  if (input.stillRunning) {
@@ -14366,31 +14381,60 @@ function applySystemCaEnv(env) {
14366
14381
  const inherited = process.env.NODE_EXTRA_CA_CERTS?.trim();
14367
14382
  if (inherited) env[NODE_EXTRA_CA_CERTS] = inherited;
14368
14383
  }
14369
- function extraCertsFromBundle() {
14384
+ function extraCertsFromBundle(io = {}) {
14370
14385
  const path2 = process.env.NODE_EXTRA_CA_CERTS?.trim();
14371
14386
  if (path2) return splitPemCertificates(readExistingFile(path2));
14372
- const dest = systemCaBundlePath();
14373
- return splitPemCertificates(readExistingFile(dest));
14387
+ return splitPemCertificates(readExistingFile(systemCaBundlePath(io)));
14388
+ }
14389
+ function extraSystemCaPems(io = {}) {
14390
+ const fromBundle = extraCertsFromBundle(io);
14391
+ if (fromBundle.length > 0) return fromBundle;
14392
+ return splitPemCertificates(collectSystemCaPem(io));
14393
+ }
14394
+ function mergeSystemCaIntoTlsOptions(options, extra, roots = import_node_tls.default.rootCertificates) {
14395
+ const existing = options?.ca;
14396
+ const base = existing == null ? [...roots] : Array.isArray(existing) ? [...existing] : [existing];
14397
+ return { ...options, ca: [...base, ...extra] };
14398
+ }
14399
+ function systemCaTlsOptions(io = {}) {
14400
+ const extra = extraSystemCaPems(io);
14401
+ if (extra.length === 0) return {};
14402
+ return { ca: [...import_node_tls.default.rootCertificates, ...extra] };
14403
+ }
14404
+ function installCreateSecureContextCaPatch(api, extra) {
14405
+ if (api.__sideboardSystemCaPatched) return true;
14406
+ if (typeof api.createSecureContext !== "function") return false;
14407
+ const orig = api.createSecureContext;
14408
+ const roots = api.rootCertificates ?? import_node_tls.default.rootCertificates;
14409
+ api.createSecureContext = ((options) => orig(mergeSystemCaIntoTlsOptions(options, extra, roots)));
14410
+ api.__sideboardSystemCaPatched = true;
14411
+ return true;
14374
14412
  }
14375
14413
  function trustSystemCertificates(io = {}) {
14376
14414
  try {
14377
- const api = import_node_tls.default;
14378
- if (typeof api.getCACertificates !== "function" || typeof api.setDefaultCACertificates !== "function") {
14379
- return false;
14380
- }
14381
- const current = api.getCACertificates("default");
14382
- const system = api.getCACertificates("system");
14383
- let extra = extraCertsFromBundle();
14384
- if (system.length === 0 && extra.length === 0) {
14385
- extra = splitPemCertificates(collectSystemCaPem(io));
14415
+ const extra = extraSystemCaPems(io);
14416
+ const api = io.tls ?? import_node_tls.default;
14417
+ if (typeof api.getCACertificates === "function" && typeof api.setDefaultCACertificates === "function") {
14418
+ const current = api.getCACertificates("default");
14419
+ const system = api.getCACertificates("system");
14420
+ if (system.length === 0 && extra.length === 0) return false;
14421
+ api.setDefaultCACertificates([...current, ...system, ...extra]);
14422
+ return true;
14386
14423
  }
14387
- if (system.length === 0 && extra.length === 0) return false;
14388
- api.setDefaultCACertificates([...current, ...system, ...extra]);
14389
- return true;
14424
+ if (extra.length === 0) return false;
14425
+ return installCreateSecureContextCaPatch(api, extra);
14390
14426
  } catch {
14391
14427
  return false;
14392
14428
  }
14393
14429
  }
14430
+ function bootstrapSystemCa(io = {}) {
14431
+ const systemCaBundle = materializeSystemCaBundle(io);
14432
+ if (systemCaBundle && !process.env.NODE_EXTRA_CA_CERTS?.trim()) {
14433
+ process.env.NODE_EXTRA_CA_CERTS = systemCaBundle;
14434
+ }
14435
+ applySystemCaEnv(process.env);
14436
+ trustSystemCertificates(io);
14437
+ }
14394
14438
  var import_node_child_process4, import_node_fs31, import_node_path34, import_node_tls, NODE_USE_SYSTEM_CA, NODE_EXTRA_CA_CERTS, BUNDLE_NAME, BUNDLE_MAX_AGE_MS, LINUX_CA_FILES, PEM_CERT_RE;
14395
14439
  var init_system_ca = __esm({
14396
14440
  "src/http/system-ca.ts"() {
@@ -22230,6 +22274,7 @@ function decisionBlockFromParts(parts, planMode, at = (/* @__PURE__ */ new Date(
22230
22274
  const reason = pending ? formatAskUserNotifyMessage(pending.questions) : "Waiting for an answer in that chat.";
22231
22275
  return {
22232
22276
  ...makeAgentBlock("ask_user", reason, at),
22277
+ onScreen: true,
22233
22278
  ...pending ? { questionSignature: pending.signature, questions: pending.questions } : {}
22234
22279
  };
22235
22280
  }
@@ -22287,6 +22332,38 @@ function dismissAskUserQuestion(threadId, signature) {
22287
22332
  ...clearBlock ? { agentBlock: null } : {}
22288
22333
  });
22289
22334
  }
22335
+ function askBlocksEqual(a, b) {
22336
+ if (a === b) return true;
22337
+ if (!a || !b) return false;
22338
+ return a.source === b.source && a.reason === b.reason && a.at === b.at && a.onScreen === b.onScreen && a.questionSignature === b.questionSignature;
22339
+ }
22340
+ function askBlockAfterLiveParts(existing, parts) {
22341
+ const last = lastTopLevelTool(parts);
22342
+ if (last?.type === "tool" && isAskUserToolName(last.name)) {
22343
+ if (existing?.source === "ask_user" && existing.onScreen) return existing;
22344
+ const pending = extractPendingPlanQuestions(parts);
22345
+ const questions2 = pending?.questions ?? existing?.questions;
22346
+ if (!questions2?.length) {
22347
+ return existing?.source === "ask_user" ? { ...existing, onScreen: true } : existing ?? null;
22348
+ }
22349
+ const at = existing?.source === "ask_user" ? existing.at : void 0;
22350
+ return {
22351
+ ...makeAgentBlock("ask_user", formatAskUserNotifyMessage(questions2), at),
22352
+ onScreen: true,
22353
+ questionSignature: pending?.signature ?? existing?.questionSignature,
22354
+ questions: questions2
22355
+ };
22356
+ }
22357
+ if (last && existing?.source === "ask_user") return null;
22358
+ return existing ?? null;
22359
+ }
22360
+ function syncAskUserBlockFromLive(threadId, parts) {
22361
+ const thread = readThread(threadId);
22362
+ if (!thread || thread.status === "archived") return;
22363
+ const next = askBlockAfterLiveParts(thread.agentBlock, parts);
22364
+ if (askBlocksEqual(thread.agentBlock, next)) return;
22365
+ updateThread(threadId, { agentBlock: next });
22366
+ }
22290
22367
  function writeAgentBlock(threadId, block) {
22291
22368
  const thread = readThread(threadId);
22292
22369
  if (!thread || thread.status === "archived") return;
@@ -25139,6 +25216,7 @@ function noteTurnLiveEvent(threadId, event) {
25139
25216
  const prev = buffers.get(threadId) ?? { parts: [], timer: null };
25140
25217
  prev.parts = trimParts(applyAgentEvent(prev.parts, event));
25141
25218
  buffers.set(threadId, prev);
25219
+ syncAskUserBlockFromLive(threadId, prev.parts);
25142
25220
  if (event.type === "exit") {
25143
25221
  flush(threadId);
25144
25222
  const cur = buffers.get(threadId);
@@ -25195,6 +25273,7 @@ var init_turn_live = __esm({
25195
25273
  import_node_fs57 = require("fs");
25196
25274
  init_message_parts();
25197
25275
  init_paths();
25276
+ init_agent_block();
25198
25277
  buffers = /* @__PURE__ */ new Map();
25199
25278
  FLUSH_MS = 800;
25200
25279
  MAX_PARTS = 80;
@@ -32088,6 +32167,7 @@ __export(index_exports, {
32088
32167
  archiveRemovedWorkspaceChats: () => archiveRemovedWorkspaceChats,
32089
32168
  archivesWithRemovedWorkspace: () => archivesWithRemovedWorkspace,
32090
32169
  armSchedules: () => armSchedules,
32170
+ askBlockStillWaiting: () => askBlockStillWaiting,
32091
32171
  assertAbleTimeCredential: () => assertAbleTimeCredential,
32092
32172
  assertAbleTimeMcpCredential: () => assertAbleTimeMcpCredential,
32093
32173
  assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
@@ -32100,6 +32180,7 @@ __export(index_exports, {
32100
32180
  autoCleanupOrphansEnabled: () => autoCleanupOrphansEnabled,
32101
32181
  autoRenameBranchEnabled: () => autoRenameBranchEnabled,
32102
32182
  autoRunAfterSetupEnabled: () => autoRunAfterSetupEnabled,
32183
+ bootstrapSystemCa: () => bootstrapSystemCa,
32103
32184
  branchDisplayLabel: () => branchDisplayLabel,
32104
32185
  brightsyAccessTokenNeedsRefresh: () => brightsyAccessTokenNeedsRefresh,
32105
32186
  brightsyAdapter: () => brightsyAdapter,
@@ -32527,6 +32608,7 @@ __export(index_exports, {
32527
32608
  lastAgentReply: () => lastAgentReply,
32528
32609
  lastAssistantMessageText: () => lastAssistantMessageText,
32529
32610
  lastRequestOccupancy: () => lastRequestOccupancy,
32611
+ lastTopLevelToolIsAskUser: () => lastTopLevelToolIsAskUser,
32530
32612
  latestPendingPlanQuestions: () => latestPendingPlanQuestions,
32531
32613
  linearAuthorizationHeader: () => linearAuthorizationHeader,
32532
32614
  linearCycleIsActive: () => linearCycleIsActive,
@@ -33440,6 +33522,7 @@ init_schedule_runner();
33440
33522
  // src/host/serve.ts
33441
33523
  var import_node_child_process7 = require("child_process");
33442
33524
  var import_node_os22 = require("os");
33525
+ init_system_ca();
33443
33526
  init_app_settings();
33444
33527
  init_desktop_host();
33445
33528
  init_schedules();
@@ -33447,6 +33530,7 @@ init_schedule_runner();
33447
33530
 
33448
33531
  // src/remote/client.ts
33449
33532
  var import_ws2 = require("ws");
33533
+ init_system_ca();
33450
33534
 
33451
33535
  // src/slack/public-dns.ts
33452
33536
  var import_node_dns = require("dns");
@@ -34796,6 +34880,7 @@ function parsePhoneCreate(value) {
34796
34880
  }
34797
34881
  function summarize(thread) {
34798
34882
  const pr = phonePrPill(thread);
34883
+ const block = visibleAgentBlock(thread);
34799
34884
  return {
34800
34885
  id: thread.id,
34801
34886
  title: thread.title?.trim() || "Untitled",
@@ -34806,14 +34891,12 @@ function summarize(thread) {
34806
34891
  ...pr?.url ? { prUrl: pr.url } : {},
34807
34892
  ...pr?.number ? { prNumber: pr.number } : {},
34808
34893
  ...pr?.status ? { prStatus: pr.status } : {},
34809
- ...thread.agentBlock ? { needsYou: true, blockedReason: thread.agentBlock.reason } : {}
34894
+ ...block ? { needsYou: true, blockedReason: block.reason } : {}
34810
34895
  };
34811
34896
  }
34812
34897
  function previewOf2(thread) {
34813
- const messages = listPhoneMessages(thread);
34814
- const last = messages[messages.length - 1];
34815
- if (!last) return "";
34816
- return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT2);
34898
+ const last = listPhoneMessages(thread).at(-1);
34899
+ return last ? clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT2) : "";
34817
34900
  }
34818
34901
  function repoName2(repoPath) {
34819
34902
  return repoPath.replace(/\/$/, "").split("/").pop() || repoPath;
@@ -35169,7 +35252,8 @@ function emitChatTurn(opts, chatId, threadId) {
35169
35252
  const pending = latestPendingPlanQuestions({
35170
35253
  messages: thread.messages,
35171
35254
  agentBlock: thread.agentBlock,
35172
- dismissedQuestionSignature: thread.dismissedQuestionSignature
35255
+ dismissedQuestionSignature: thread.dismissedQuestionSignature,
35256
+ status: thread.status
35173
35257
  });
35174
35258
  if (pending && result.taskState !== "failed" && result.taskState !== "canceled" && (result.taskState === "input-required" || thread.agentBlock?.source === "ask_user")) {
35175
35259
  emitControl(opts, {
@@ -35540,7 +35624,8 @@ function republishPendingAsk(opts, chatId) {
35540
35624
  const pending = latestPendingPlanQuestions({
35541
35625
  messages: thread.messages,
35542
35626
  agentBlock: thread.agentBlock,
35543
- dismissedQuestionSignature: thread.dismissedQuestionSignature
35627
+ dismissedQuestionSignature: thread.dismissedQuestionSignature,
35628
+ status: thread.status
35544
35629
  });
35545
35630
  if (!pending) return;
35546
35631
  emitControl(opts, {
@@ -37803,7 +37888,8 @@ function runCoordinatorPrompt(opts, body, generation) {
37803
37888
  const pending = latestPendingPlanQuestions({
37804
37889
  messages: thread.messages,
37805
37890
  agentBlock: thread.agentBlock,
37806
- dismissedQuestionSignature: thread.dismissedQuestionSignature
37891
+ dismissedQuestionSignature: thread.dismissedQuestionSignature,
37892
+ status: thread.status
37807
37893
  });
37808
37894
  if (pending && result.taskState !== "failed" && result.taskState !== "canceled" && (result.taskState === "input-required" || thread.agentBlock?.source === "ask_user")) {
37809
37895
  opts.onOutbound({
@@ -38252,7 +38338,15 @@ init_paths();
38252
38338
  init_thread_store();
38253
38339
  var PHONE_BOARD_SYNC_MS = 300;
38254
38340
  function chatKey(row2) {
38255
- return [row2.id, row2.title, row2.status, row2.agent, row2.preview].join(" ");
38341
+ return [
38342
+ row2.id,
38343
+ row2.title,
38344
+ row2.status,
38345
+ row2.agent,
38346
+ row2.preview,
38347
+ row2.needsYou ? "1" : "0",
38348
+ row2.blockedReason ?? ""
38349
+ ].join(" ");
38256
38350
  }
38257
38351
  function phoneBoardKey(sidebar) {
38258
38352
  const orchestration = sidebar.orchestration.map(chatKey).join("\n");
@@ -38363,6 +38457,14 @@ function startPhoneBoardSync(opts) {
38363
38457
  init_protocol();
38364
38458
  var BACKOFF_START_MS = 1e3;
38365
38459
  var BACKOFF_MAX_MS = 3e4;
38460
+ var RELAY_TLS_RE = /UNABLE_TO_GET_ISSUER|unable to get local issuer|SELF_SIGNED_CERT_IN_CHAIN/i;
38461
+ var RELAY_TLS_HINT = " \u2014 the TLS certificate was rejected. GitHub login can still work (browser Keychain trust); the relay socket uses Node TLS, which needs the corporate CA.";
38462
+ function formatRemoteHostError(err) {
38463
+ const message = err instanceof Error ? err.message : String(err);
38464
+ if (message === "aborted") return message;
38465
+ if (RELAY_TLS_RE.test(message)) return `${message}${RELAY_TLS_HINT}`;
38466
+ return message;
38467
+ }
38366
38468
  function createRemoteKeepalive(opts) {
38367
38469
  let pongTimer = null;
38368
38470
  const arm = () => {
@@ -38459,7 +38561,7 @@ function runRemoteHost(opts) {
38459
38561
  backoff = BACKOFF_START_MS;
38460
38562
  } catch (err) {
38461
38563
  if (opts.signal?.aborted) return;
38462
- const message = err instanceof Error ? err.message : String(err);
38564
+ const message = formatRemoteHostError(err);
38463
38565
  if (message !== "aborted") log(`remote error: ${message}`);
38464
38566
  }
38465
38567
  opts.onStatus?.({ connected: false, phoneConnected: false });
@@ -38477,7 +38579,10 @@ function runRemoteHost(opts) {
38477
38579
  const gen = ++connectionGen;
38478
38580
  stopBoardSync();
38479
38581
  setPhoneOpenChat(null);
38480
- const ws = new import_ws2.WebSocket(opts.url, { lookup: lookupPreferPublicDns });
38582
+ const ws = new import_ws2.WebSocket(opts.url, {
38583
+ lookup: lookupPreferPublicDns,
38584
+ ...systemCaTlsOptions()
38585
+ });
38481
38586
  socket = ws;
38482
38587
  let settled = false;
38483
38588
  const finish2 = (err) => {
@@ -40881,6 +40986,7 @@ async function startHeadlessHost(opts) {
40881
40986
  const blocked = headlessHostBlockedReason();
40882
40987
  if (blocked) return { ok: false, reason: blocked };
40883
40988
  claimDesktopHost();
40989
+ bootstrapSystemCa();
40884
40990
  armSchedules();
40885
40991
  const ac = new AbortController();
40886
40992
  let stopped = false;
@@ -45465,6 +45571,7 @@ function getSlackReplyTarget(threadId) {
45465
45571
  }
45466
45572
 
45467
45573
  // src/slack/relay-client.ts
45574
+ init_system_ca();
45468
45575
  var import_ws6 = require("ws");
45469
45576
  var CLAIM_FALLBACK_MS = 400;
45470
45577
  var BACKOFF_START_MS3 = 1e3;
@@ -45493,7 +45600,10 @@ async function runSlackRelayClient(opts) {
45493
45600
  const log = opts.onLog ?? (() => void 0);
45494
45601
  const Ws = opts.WebSocketImpl ? resolveWebSocket(opts.WebSocketImpl) : class {
45495
45602
  constructor(url2) {
45496
- return new import_ws6.WebSocket(url2, { lookup: lookupPreferPublicDns });
45603
+ return new import_ws6.WebSocket(url2, {
45604
+ lookup: lookupPreferPublicDns,
45605
+ ...systemCaTlsOptions()
45606
+ });
45497
45607
  }
45498
45608
  };
45499
45609
  const url = opts.url.trim();
@@ -46316,12 +46426,12 @@ function sleep2(ms, signal) {
46316
46426
  signal?.addEventListener("abort", onAbort, { once: true });
46317
46427
  });
46318
46428
  }
46319
- var RELAY_TLS_HINT = " \u2014 the TLS certificate was rejected. A work proxy often does this when Sideboard is not allowlisted, or when the app does not trust the corporate CA.";
46429
+ var RELAY_TLS_HINT2 = " \u2014 the TLS certificate was rejected. A work proxy often does this when Sideboard is not allowlisted, or when the app does not trust the corporate CA.";
46320
46430
  var RELAY_NETWORK_HINT = " Sideboard could not reach the relay. On a work computer this is often a VPN, a proxy, or an allowlist that does not include Sideboard.";
46321
46431
  var RELAY_NETWORK_RE = /fetch failed|Failed to fetch|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|EAI_AGAIN|EPERM|UND_ERR_|ERR_CONNECTION|ERR_NETWORK|ERR_PROXY|ERR_TUNNEL|ERR_INTERNET|ERR_BLOCKED|ERR_NAME_NOT_RESOLVED|ERR_ADDRESS_UNREACHABLE|ERR_CERT|CERT_|UNABLE_TO_GET_ISSUER|SELF_SIGNED/i;
46322
46432
  function relayFetchError(err, url) {
46323
- const formatted = formatFetchError(err, url, { tlsHint: RELAY_TLS_HINT });
46324
- if (formatted.includes(RELAY_TLS_HINT) || !RELAY_NETWORK_RE.test(formatted)) {
46433
+ const formatted = formatFetchError(err, url, { tlsHint: RELAY_TLS_HINT2 });
46434
+ if (formatted.includes(RELAY_TLS_HINT2) || !RELAY_NETWORK_RE.test(formatted)) {
46325
46435
  return new Error(formatted);
46326
46436
  }
46327
46437
  return new Error(`${formatted}${RELAY_NETWORK_HINT}`);
@@ -46955,6 +47065,7 @@ async function pollSlackOutboundWatches(opts) {
46955
47065
  archiveRemovedWorkspaceChats,
46956
47066
  archivesWithRemovedWorkspace,
46957
47067
  armSchedules,
47068
+ askBlockStillWaiting,
46958
47069
  assertAbleTimeCredential,
46959
47070
  assertAbleTimeMcpCredential,
46960
47071
  assertOrchestratorCapableAgent,
@@ -46967,6 +47078,7 @@ async function pollSlackOutboundWatches(opts) {
46967
47078
  autoCleanupOrphansEnabled,
46968
47079
  autoRenameBranchEnabled,
46969
47080
  autoRunAfterSetupEnabled,
47081
+ bootstrapSystemCa,
46970
47082
  branchDisplayLabel,
46971
47083
  brightsyAccessTokenNeedsRefresh,
46972
47084
  brightsyAdapter,
@@ -47394,6 +47506,7 @@ async function pollSlackOutboundWatches(opts) {
47394
47506
  lastAgentReply,
47395
47507
  lastAssistantMessageText,
47396
47508
  lastRequestOccupancy,
47509
+ lastTopLevelToolIsAskUser,
47397
47510
  latestPendingPlanQuestions,
47398
47511
  linearAuthorizationHeader,
47399
47512
  linearCycleIsActive,