@indigoai-us/hq-cli 5.234.2 → 5.236.0
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/CHANGELOG.md +56 -0
- package/assets/bot-workers/setup/skills/agents-and-team.md +154 -0
- package/assets/bot-workers/setup/skills/first-company.md +4 -1
- package/assets/bot-workers/setup/skills/pricing.md +92 -0
- package/assets/bot-workers/setup/skills/standing-help.md +7 -0
- package/assets/bot-workers/setup/worker.yaml +244 -63
- package/dist/command-catalog.generated.d.ts +3 -0
- package/dist/command-catalog.generated.js +4 -0
- package/dist/commands/bot.d.ts +2 -0
- package/dist/commands/bot.js +14 -2
- package/dist/commands/lanes-checks.d.ts +0 -3
- package/dist/commands/lanes-checks.js +22 -40
- package/dist/commands/lanes-import.d.ts +0 -2
- package/dist/commands/lanes-import.js +3 -7
- package/dist/commands/lanes-report.js +5 -12
- package/dist/commands/lanes-tracker.d.ts +0 -3
- package/dist/commands/lanes-tracker.js +41 -17
- package/dist/commands/lanes.d.ts +0 -5
- package/dist/commands/lanes.js +3 -329
- package/dist/lib/bot/config.d.ts +12 -0
- package/dist/lib/bot/config.js +25 -0
- package/dist/lib/bot/local-config.d.ts +2 -1
- package/dist/lib/bot/local-config.js +4 -0
- package/dist/lib/bot/progress.d.ts +25 -0
- package/dist/lib/bot/progress.js +39 -1
- package/dist/lib/bot/prompt.d.ts +2 -0
- package/dist/lib/bot/prompt.js +12 -2
- package/dist/lib/bot/run.js +10 -2
- package/dist/lib/lanes/reconcile.d.ts +0 -2
- package/dist/lib/lanes/reconcile.js +0 -16
- package/dist/lib/lanes/store.js +1 -9
- package/dist/lib/lanes/tracker-store.d.ts +2 -6
- package/dist/lib/lanes/tracker-store.js +1 -1
- package/dist/lib/lanes/types.d.ts +0 -4
- package/dist/node-network-compat.d.ts +1 -10
- package/dist/node-network-compat.js +3 -70
- package/package.json +1 -1
- package/dist/lib/lanes/tracker-mesh-questions.d.ts +0 -54
- package/dist/lib/lanes/tracker-mesh-questions.js +0 -138
- package/dist/lib/lanes/tracker-mesh.d.ts +0 -19
- package/dist/lib/lanes/tracker-mesh.js +0 -251
- package/dist/node-network-compat-flag.d.ts +0 -19
- package/dist/node-network-compat-flag.js +0 -87
package/dist/commands/lanes.js
CHANGED
|
@@ -17,13 +17,11 @@ import { registerLaneChecksCommand } from "./lanes-checks.js";
|
|
|
17
17
|
import { registerLaneReportCommands } from "./lanes-report.js";
|
|
18
18
|
import { registerLaneTrackerImportCommand } from "./lanes-import.js";
|
|
19
19
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
20
|
-
import { createTrackerMeshQuestionClient, deliverTrackerMeshQuestionOnce } from "../lib/lanes/tracker-mesh-questions.js";
|
|
21
|
-
import { createWorkMeshTrackerStore } from "../lib/lanes/tracker-mesh.js";
|
|
22
20
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
23
21
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
24
22
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
25
23
|
import { resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
26
|
-
import { defaultTrackerActor, LANES_TRACKER_FLAG,
|
|
24
|
+
import { defaultTrackerActor, LANES_TRACKER_FLAG, registerLaneTrackerCommands, } from "./lanes-tracker.js";
|
|
27
25
|
import { createOutpostHireGrantContext, parseOutpostHireGrantContext, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
|
|
28
26
|
import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } from "../lib/lanes/outpost-profile-repin.js";
|
|
29
27
|
import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
|
|
@@ -46,7 +44,7 @@ import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_
|
|
|
46
44
|
import { activeLaneStories, laneStories, laneStory, storyWarning, } from "../lib/lanes/stories.js";
|
|
47
45
|
import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
|
|
48
46
|
import { keepAliveExclusion, LEAD_LANE_KEEP_ALIVE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
49
|
-
import { questionBodyAbsPath,
|
|
47
|
+
import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
|
|
50
48
|
import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
|
|
51
49
|
import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
|
|
52
50
|
import { assertUniqueAccountHomes, providerCompactAfterMs } from "../lib/lanes/config.js";
|
|
@@ -81,9 +79,6 @@ function defaultLanePlacementEnabled() {
|
|
|
81
79
|
function defaultLaneTrackerEnabled(company) {
|
|
82
80
|
return readLanesRegistryFlag(LANES_TRACKER_FLAG, { company });
|
|
83
81
|
}
|
|
84
|
-
function defaultLaneTrackerMeshEnabled(company) {
|
|
85
|
-
return readLanesRegistryFlag(LANES_TRACKER_MESH_FLAG, { company });
|
|
86
|
-
}
|
|
87
82
|
function defaultLaneOutpostQuestionVisibilityEnabled(company, env) {
|
|
88
83
|
return readLanesRegistryFlag(LANE_OUTPOST_QUESTION_VISIBILITY_FLAG, {
|
|
89
84
|
company,
|
|
@@ -319,8 +314,6 @@ function resolveDeps(deps = {}) {
|
|
|
319
314
|
laneOutpostQuestionVisibilityEnabled: deps.laneOutpostQuestionVisibilityEnabled ??
|
|
320
315
|
((company) => defaultLaneOutpostQuestionVisibilityEnabled(company, env)),
|
|
321
316
|
laneTrackerEnabled: deps.laneTrackerEnabled ?? defaultLaneTrackerEnabled,
|
|
322
|
-
laneTrackerMeshEnabled: deps.laneTrackerMeshEnabled ?? defaultLaneTrackerMeshEnabled,
|
|
323
|
-
trackerMeshRequest: deps.trackerMeshRequest,
|
|
324
317
|
laneHostScopedAccountHomesEnabled: deps.laneHostScopedAccountHomesEnabled ??
|
|
325
318
|
((company) => defaultLaneHostScopedAccountHomesEnabled(company, env)),
|
|
326
319
|
trackerActor: deps.trackerActor ?? defaultTrackerActor,
|
|
@@ -510,167 +503,6 @@ function requireQuestionBody(hqRoot, question) {
|
|
|
510
503
|
}
|
|
511
504
|
return body;
|
|
512
505
|
}
|
|
513
|
-
function trackerMeshAudience(ref) {
|
|
514
|
-
if (ref.kind === "user")
|
|
515
|
-
return "owner";
|
|
516
|
-
if (ref.kind === "session")
|
|
517
|
-
return "operator";
|
|
518
|
-
return `lane:${ref.id ?? ""}`;
|
|
519
|
-
}
|
|
520
|
-
function trackerMeshAudienceKind(value) {
|
|
521
|
-
if (value === "owner" || value.startsWith("owner:"))
|
|
522
|
-
return "owner";
|
|
523
|
-
if (value === "operator" || value.startsWith("operator:"))
|
|
524
|
-
return "operator";
|
|
525
|
-
return "lane";
|
|
526
|
-
}
|
|
527
|
-
function trackerMeshQuestionInput(lane, question, body) {
|
|
528
|
-
if (!lane.project_id.trim())
|
|
529
|
-
throw new LanesError("tracker_mesh_question_invalid", "Question lane has no project id", { lane_id: lane.lane_id });
|
|
530
|
-
const route = question.routing_history;
|
|
531
|
-
const currentAudience = route.at(-1)?.audience ?? lane.senior;
|
|
532
|
-
return {
|
|
533
|
-
laneId: lane.lane_id,
|
|
534
|
-
...(lane.thread_id ? { threadId: lane.thread_id } : {}),
|
|
535
|
-
projectId: lane.project_id,
|
|
536
|
-
audience: trackerMeshAudienceKind(trackerMeshAudience(currentAudience)),
|
|
537
|
-
audienceChain: route.length ? route.map((entry) => trackerMeshAudience(entry.audience)) : [trackerMeshAudience(currentAudience)],
|
|
538
|
-
routingHistory: route.map((entry) => ({ audience: trackerMeshAudience(entry.audience), at: entry.at, reason: entry.reason })),
|
|
539
|
-
question: body.question,
|
|
540
|
-
context: body.context,
|
|
541
|
-
options: body.options,
|
|
542
|
-
...(body.recommended !== undefined ? { recommended: body.recommended } : {}),
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
function assertMeshQuestionMatchesLocal(row, question, company) {
|
|
546
|
-
if (row.companyUid !== company.uid || row.laneId !== question.lane_id || row.projectId !== question.project_id) {
|
|
547
|
-
throw new LanesError("tracker_question_company_mismatch", "Work Mesh question does not match the local lane company and project", {
|
|
548
|
-
question_id: question.question_id,
|
|
549
|
-
mesh_question_id: row.questionId,
|
|
550
|
-
lane_id: question.lane_id,
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
async function indexTrackerQuestion(ctx, hqRoot, lane, question, body, company, client = createTrackerMeshQuestionClient({ company, request: ctx.trackerMeshRequest })) {
|
|
555
|
-
if (question.mesh_question_id)
|
|
556
|
-
return question;
|
|
557
|
-
if (question.status !== "pending")
|
|
558
|
-
return question;
|
|
559
|
-
const input = trackerMeshQuestionInput(lane, question, body);
|
|
560
|
-
const candidates = (await client.list(["pending", "answered"])).filter((row) => row.laneId === input.laneId && row.projectId === input.projectId && row.threadId === (input.threadId ?? row.threadId) &&
|
|
561
|
-
row.audience === input.audience && row.question === input.question && row.context === input.context &&
|
|
562
|
-
JSON.stringify(row.options) === JSON.stringify(input.options) &&
|
|
563
|
-
row.routingHistory.at(-1)?.at === input.routingHistory.at(-1)?.at);
|
|
564
|
-
if (candidates.length > 1) {
|
|
565
|
-
throw new LanesError("tracker_mesh_question_ambiguous", "More than one pending Work Mesh question matches this local question", { question_id: question.question_id, lane_id: lane.lane_id });
|
|
566
|
-
}
|
|
567
|
-
const row = candidates[0] ?? await client.create(input);
|
|
568
|
-
assertMeshQuestionMatchesLocal(row, question, company);
|
|
569
|
-
return casUpdateQuestion(hqRoot, question.question_id, (current) => {
|
|
570
|
-
if (current.status !== "pending")
|
|
571
|
-
return current;
|
|
572
|
-
if (current.mesh_question_id && current.mesh_question_id !== row.questionId) {
|
|
573
|
-
throw new LanesError("tracker_mesh_question_conflict", "Local question was already linked to another Work Mesh question", { question_id: current.question_id });
|
|
574
|
-
}
|
|
575
|
-
return current.mesh_question_id === row.questionId ? current : { ...current, mesh_question_id: row.questionId };
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
async function syncTrackerMeshQuestions(ctx, hqRoot) {
|
|
579
|
-
if (!ctx.laneTrackerMeshEnabled)
|
|
580
|
-
return;
|
|
581
|
-
const lanes = new Map(loadAllLanes(hqRoot).map((lane) => [lane.lane_id, lane]));
|
|
582
|
-
const byCompany = new Map();
|
|
583
|
-
for (const lane of lanes.values()) {
|
|
584
|
-
let company;
|
|
585
|
-
try {
|
|
586
|
-
company = await resolveQuestionCompanyWithFallback(hqRoot, lane, () => resolveActiveQuestionCompany(ctx, "Cannot sync lane questions without an HQ company"));
|
|
587
|
-
}
|
|
588
|
-
catch (err) {
|
|
589
|
-
if (lane.company || lane.mesh_company ||
|
|
590
|
-
!isLanesError(err) || err.error !== "question_company_unavailable" ||
|
|
591
|
-
err.details.resolution !== "active_company_required")
|
|
592
|
-
throw err;
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
if (!await ctx.laneTrackerMeshEnabled(company))
|
|
596
|
-
continue;
|
|
597
|
-
const key = `${company.uid}\0${company.slug}`;
|
|
598
|
-
if (!byCompany.has(key))
|
|
599
|
-
byCompany.set(key, { company, client: createTrackerMeshQuestionClient({ company, request: ctx.trackerMeshRequest }) });
|
|
600
|
-
}
|
|
601
|
-
const localQuestions = loadAllQuestions(hqRoot);
|
|
602
|
-
for (const question of localQuestions) {
|
|
603
|
-
if (question.status !== "pending" || question.mesh_question_id)
|
|
604
|
-
continue;
|
|
605
|
-
const lane = lanes.get(question.lane_id);
|
|
606
|
-
if (!lane)
|
|
607
|
-
continue;
|
|
608
|
-
const company = question.company ?? lane.company ?? lane.mesh_company;
|
|
609
|
-
if (!company)
|
|
610
|
-
continue;
|
|
611
|
-
const client = byCompany.get(`${company.uid}\0${company.slug}`)?.client;
|
|
612
|
-
if (!client)
|
|
613
|
-
continue;
|
|
614
|
-
const body = requireQuestionBody(hqRoot, question);
|
|
615
|
-
await indexTrackerQuestion(ctx, hqRoot, lane, question, body, company, client);
|
|
616
|
-
}
|
|
617
|
-
for (const { company, client } of byCompany.values()) {
|
|
618
|
-
const remote = await client.list(["answered"]);
|
|
619
|
-
const remoteById = new Map(remote.map((row) => [row.questionId, row]));
|
|
620
|
-
for (const question of loadAllQuestions(hqRoot)) {
|
|
621
|
-
if (!question.mesh_question_id || question.company?.uid !== company.uid)
|
|
622
|
-
continue;
|
|
623
|
-
const row = remoteById.get(question.mesh_question_id);
|
|
624
|
-
if (!row)
|
|
625
|
-
continue;
|
|
626
|
-
assertMeshQuestionMatchesLocal(row, question, company);
|
|
627
|
-
const lane = lanes.get(question.lane_id);
|
|
628
|
-
if (!lane)
|
|
629
|
-
continue;
|
|
630
|
-
if (row.status === "answered" && question.status === "pending") {
|
|
631
|
-
if (typeof row.choice !== "string" || typeof row.answeredAt !== "string" || typeof row.answeredBy !== "string") {
|
|
632
|
-
throw new LanesError("tracker_mesh_response_invalid", "Answered Work Mesh question is missing its answer fields", { mesh_question_id: row.questionId });
|
|
633
|
-
}
|
|
634
|
-
const currentBody = requireQuestionBody(hqRoot, question);
|
|
635
|
-
let notesRef;
|
|
636
|
-
let nextBody = question.body;
|
|
637
|
-
if (row.notes !== undefined) {
|
|
638
|
-
const bodyQuestion = question.company || question.body.store === "personal" ? question : { ...question, company };
|
|
639
|
-
const stored = writeQuestionBody(hqRoot, bodyQuestion, { ...currentBody, notes: row.notes });
|
|
640
|
-
notesRef = stored.ref;
|
|
641
|
-
nextBody = stored;
|
|
642
|
-
}
|
|
643
|
-
casUpdateQuestion(hqRoot, question.question_id, (current) => {
|
|
644
|
-
if (current.status !== "pending")
|
|
645
|
-
return current;
|
|
646
|
-
if (current.mesh_question_id !== row.questionId)
|
|
647
|
-
throw new LanesError("tracker_mesh_question_conflict", "Local question link changed during answer sync", { question_id: current.question_id });
|
|
648
|
-
return {
|
|
649
|
-
...current,
|
|
650
|
-
company: current.company ?? company,
|
|
651
|
-
body: nextBody,
|
|
652
|
-
status: "answered",
|
|
653
|
-
answer: {
|
|
654
|
-
choice: row.choice,
|
|
655
|
-
answer_kind: currentBody.options.includes(row.choice) ? "option" : "freeform",
|
|
656
|
-
notes_ref: notesRef,
|
|
657
|
-
answered_by: row.answeredBy,
|
|
658
|
-
answered_at: row.answeredAt,
|
|
659
|
-
answered_level: current.hop,
|
|
660
|
-
},
|
|
661
|
-
};
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
for (const question of loadAllQuestions(hqRoot)) {
|
|
666
|
-
if (question.company?.uid !== company.uid || !question.mesh_question_id)
|
|
667
|
-
continue;
|
|
668
|
-
await deliverTrackerMeshQuestionOnce(question, company, client, (deliveredAt) => casUpdateQuestion(hqRoot, question.question_id, (current) => current.mesh_delivered_at
|
|
669
|
-
? current
|
|
670
|
-
: { ...current, mesh_delivered_at: deliveredAt }));
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
506
|
function loadAllLanes(hqRoot) {
|
|
675
507
|
return listLaneIds(hqRoot).map((id) => readLane(hqRoot, id));
|
|
676
508
|
}
|
|
@@ -7996,7 +7828,7 @@ async function runAsk(ctx, opts) {
|
|
|
7996
7828
|
const attempts = ASK_ATTEMPTS;
|
|
7997
7829
|
for (let i = 0; i < attempts; i += 1) {
|
|
7998
7830
|
try {
|
|
7999
|
-
|
|
7831
|
+
const withBody = raiseLaneQuestion({
|
|
8000
7832
|
hqRoot,
|
|
8001
7833
|
lane,
|
|
8002
7834
|
body,
|
|
@@ -8006,9 +7838,6 @@ async function runAsk(ctx, opts) {
|
|
|
8006
7838
|
questionId,
|
|
8007
7839
|
});
|
|
8008
7840
|
questionId = withBody.question_id;
|
|
8009
|
-
if (await ctx.laneTrackerMeshEnabled(questionCompany)) {
|
|
8010
|
-
withBody = await indexTrackerQuestion(ctx, hqRoot, lane, withBody, body, questionCompany);
|
|
8011
|
-
}
|
|
8012
7841
|
await emitLaneMeshEvent(ctx, lane.mesh_company ?? lane.company, lane.thread_id, "lane_question", {
|
|
8013
7842
|
lane_id: lane.lane_id,
|
|
8014
7843
|
question_id: withBody.question_id,
|
|
@@ -8395,28 +8224,18 @@ async function runQuestionsList(ctx, opts) {
|
|
|
8395
8224
|
const outpostLoad = opts.allHosts
|
|
8396
8225
|
? await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot, company)
|
|
8397
8226
|
: { questions: [], errors: [] };
|
|
8398
|
-
const meshRows = await ctx.laneTrackerMeshEnabled(company) && status !== "expired"
|
|
8399
|
-
? await createTrackerMeshQuestionClient({ company, request: ctx.trackerMeshRequest }).list(status ? [status] : undefined)
|
|
8400
|
-
: [];
|
|
8401
8227
|
for (const owner of outpostLoad.questions) {
|
|
8402
8228
|
assertQuestionCompanyAccess(owner.question, owner.lane, company, hqRoot);
|
|
8403
8229
|
syncPrimaryQuestionDelivery(hqRoot, owner);
|
|
8404
8230
|
}
|
|
8405
8231
|
const remoteQuestionIds = new Set(outpostLoad.questions.map(({ question }) => question.question_id));
|
|
8406
|
-
const localMeshQuestionIds = new Set(localQuestions.map((question) => question.mesh_question_id).filter((id) => Boolean(id)));
|
|
8407
8232
|
const questions = [
|
|
8408
8233
|
...localQuestions
|
|
8409
8234
|
.filter((q) => !remoteQuestionIds.has(q.question_id))
|
|
8410
8235
|
.map((q) => {
|
|
8411
|
-
const meshRow = q.mesh_question_id ? meshRows.find((row) => row.questionId === q.mesh_question_id) : undefined;
|
|
8412
8236
|
const row = {
|
|
8413
8237
|
...q,
|
|
8414
8238
|
audience: resolveAudience(hqRoot, q, ctx),
|
|
8415
|
-
...(meshRow ? {
|
|
8416
|
-
mesh_question_id: meshRow.questionId,
|
|
8417
|
-
body_preview: { question: meshRow.question, context: meshRow.context ?? "", options: meshRow.options, ...(meshRow.recommended ? { recommended: meshRow.recommended } : {}) },
|
|
8418
|
-
host: `${meshRow.host.kind}:${meshRow.host.id}`,
|
|
8419
|
-
} : {}),
|
|
8420
8239
|
...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
|
|
8421
8240
|
};
|
|
8422
8241
|
if (!opts.allHosts)
|
|
@@ -8448,50 +8267,6 @@ async function runQuestionsList(ctx, opts) {
|
|
|
8448
8267
|
audience,
|
|
8449
8268
|
...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
|
|
8450
8269
|
})),
|
|
8451
|
-
...meshRows
|
|
8452
|
-
.filter((row) => {
|
|
8453
|
-
if (localMeshQuestionIds.has(row.questionId))
|
|
8454
|
-
return false;
|
|
8455
|
-
if (status && row.status !== status)
|
|
8456
|
-
return false;
|
|
8457
|
-
if (opts.from && row.laneId !== opts.from)
|
|
8458
|
-
return false;
|
|
8459
|
-
if (opts.project && row.projectId !== opts.project)
|
|
8460
|
-
return false;
|
|
8461
|
-
if (opts.story) {
|
|
8462
|
-
const lane = localLanes.get(row.laneId);
|
|
8463
|
-
if (!lane || lane.story_id !== opts.story)
|
|
8464
|
-
return false;
|
|
8465
|
-
}
|
|
8466
|
-
const audience = row.audience === "owner"
|
|
8467
|
-
? { kind: "user" }
|
|
8468
|
-
: row.audience === "operator"
|
|
8469
|
-
? { kind: "session", id: "mesh" }
|
|
8470
|
-
: { kind: "lane", id: row.laneId };
|
|
8471
|
-
if (forFilter && !audienceMatches(audience, forFilter))
|
|
8472
|
-
return false;
|
|
8473
|
-
return true;
|
|
8474
|
-
})
|
|
8475
|
-
.map((row) => ({
|
|
8476
|
-
question_id: row.questionId,
|
|
8477
|
-
mesh_question_id: row.questionId,
|
|
8478
|
-
lane_id: row.laneId,
|
|
8479
|
-
company,
|
|
8480
|
-
project_id: row.projectId,
|
|
8481
|
-
story_id: localLanes.get(row.laneId)?.story_id ?? "",
|
|
8482
|
-
...(row.threadId ? { thread_id: row.threadId } : {}),
|
|
8483
|
-
asked_at: row.askedAt,
|
|
8484
|
-
origin: { kind: "lane", id: row.laneId },
|
|
8485
|
-
hop: row.routingHistory.length || 1,
|
|
8486
|
-
status: row.status,
|
|
8487
|
-
audience: row.audience,
|
|
8488
|
-
host: `${row.host.kind}:${row.host.id}`,
|
|
8489
|
-
question: row.question,
|
|
8490
|
-
context: row.context ?? "",
|
|
8491
|
-
options: row.options,
|
|
8492
|
-
...(row.recommended ? { recommended: row.recommended } : {}),
|
|
8493
|
-
...(row.choice !== undefined ? { answer: { choice: row.choice, answered_by: row.answeredBy, answered_at: row.answeredAt, notes: row.notes } } : {}),
|
|
8494
|
-
})),
|
|
8495
8270
|
];
|
|
8496
8271
|
printJson({
|
|
8497
8272
|
ok: true,
|
|
@@ -8509,43 +8284,6 @@ async function runQuestionsShow(ctx, questionId) {
|
|
|
8509
8284
|
catch (err) {
|
|
8510
8285
|
if (!isLanesError(err) || err.error !== "question_not_found")
|
|
8511
8286
|
throw err;
|
|
8512
|
-
let meshCompany = null;
|
|
8513
|
-
try {
|
|
8514
|
-
meshCompany = await resolveActiveQuestionCompany(ctx, "Cannot look up Work Mesh question without an active company");
|
|
8515
|
-
}
|
|
8516
|
-
catch (companyError) {
|
|
8517
|
-
if (questionRecordRefusalCode(companyError) !== "question_company_unavailable")
|
|
8518
|
-
throw companyError;
|
|
8519
|
-
// The Outpost lookup below remains available to unscoped primary hosts.
|
|
8520
|
-
}
|
|
8521
|
-
if (meshCompany && await ctx.laneTrackerMeshEnabled(meshCompany)) {
|
|
8522
|
-
const rows = await createTrackerMeshQuestionClient({ company: meshCompany, request: ctx.trackerMeshRequest }).list();
|
|
8523
|
-
const matches = rows.filter((row) => row.questionId === questionId);
|
|
8524
|
-
if (matches.length > 1)
|
|
8525
|
-
throw new LanesError("tracker_mesh_question_ambiguous", `Work Mesh question ${questionId} is duplicated in the company inbox`, { question_id: questionId });
|
|
8526
|
-
const row = matches[0];
|
|
8527
|
-
if (row) {
|
|
8528
|
-
printJson({
|
|
8529
|
-
ok: true,
|
|
8530
|
-
action: "questions.show",
|
|
8531
|
-
question: {
|
|
8532
|
-
question_id: row.questionId,
|
|
8533
|
-
mesh_question_id: row.questionId,
|
|
8534
|
-
lane_id: row.laneId,
|
|
8535
|
-
company: meshCompany,
|
|
8536
|
-
project_id: row.projectId,
|
|
8537
|
-
thread_id: row.threadId,
|
|
8538
|
-
asked_at: row.askedAt,
|
|
8539
|
-
status: row.status,
|
|
8540
|
-
audience: row.audience,
|
|
8541
|
-
host: `${row.host.kind}:${row.host.id}`,
|
|
8542
|
-
},
|
|
8543
|
-
audience: row.audience,
|
|
8544
|
-
body: { question: row.question, context: row.context ?? "", options: row.options, ...(row.recommended ? { recommended: row.recommended } : {}), ...(row.notes ? { notes: row.notes } : {}) },
|
|
8545
|
-
});
|
|
8546
|
-
return;
|
|
8547
|
-
}
|
|
8548
|
-
}
|
|
8549
8287
|
const lookup = await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot);
|
|
8550
8288
|
if (lookup.errors.length > 0) {
|
|
8551
8289
|
throw new LanesError("outpost_question_lookup_unavailable", `Could not verify whether Outposts own question ${questionId}`, {
|
|
@@ -8601,42 +8339,6 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
|
|
|
8601
8339
|
if (!isLanesError(err) || err.error !== "question_not_found")
|
|
8602
8340
|
throw err;
|
|
8603
8341
|
const company = explicitCompany ?? await resolveActiveQuestionCompany(ctx, "Cannot look up this lane question because the active HQ company could not be resolved; re-run with --company <slug>");
|
|
8604
|
-
if (await ctx.laneTrackerMeshEnabled(company)) {
|
|
8605
|
-
const client = createTrackerMeshQuestionClient({ company, request: ctx.trackerMeshRequest });
|
|
8606
|
-
const matches = (await client.list(["pending", "answered"])).filter((row) => row.questionId === questionId);
|
|
8607
|
-
if (matches.length > 1)
|
|
8608
|
-
throw new LanesError("tracker_mesh_question_ambiguous", `Work Mesh question ${questionId} is duplicated in the company inbox`, { question_id: questionId });
|
|
8609
|
-
const row = matches[0];
|
|
8610
|
-
if (row) {
|
|
8611
|
-
if (row.status !== "pending")
|
|
8612
|
-
throw new LanesError("question_not_pending", `Question ${questionId} is ${row.status}`, { question_id: questionId, status: row.status });
|
|
8613
|
-
const freeform = opts.freeform?.trim() || undefined;
|
|
8614
|
-
if (freeform !== undefined && row.options.length === 0)
|
|
8615
|
-
throw new LanesError("freeform_not_allowed", "--freeform is only allowed for a question with options", { question_id: questionId });
|
|
8616
|
-
if (!freeform && !opts.choice)
|
|
8617
|
-
throw new LanesError("missing_choice", "--choice or --freeform is required", {});
|
|
8618
|
-
const choice = freeform ?? resolveAnswerChoice(opts.choice, row.options, false);
|
|
8619
|
-
const answer = await client.answer(questionId, choice, opts.notes);
|
|
8620
|
-
printJson({
|
|
8621
|
-
ok: true,
|
|
8622
|
-
action: "questions.answer",
|
|
8623
|
-
lane_id: answer.laneId,
|
|
8624
|
-
question: {
|
|
8625
|
-
question_id: answer.questionId,
|
|
8626
|
-
mesh_question_id: answer.questionId,
|
|
8627
|
-
lane_id: answer.laneId,
|
|
8628
|
-
project_id: answer.projectId,
|
|
8629
|
-
status: answer.status,
|
|
8630
|
-
choice: answer.choice,
|
|
8631
|
-
answered_by: answer.answeredBy,
|
|
8632
|
-
answered_at: answer.answeredAt,
|
|
8633
|
-
delivered_at: answer.deliveredAt ?? null,
|
|
8634
|
-
},
|
|
8635
|
-
delivery: "awaiting_host_poll",
|
|
8636
|
-
});
|
|
8637
|
-
return;
|
|
8638
|
-
}
|
|
8639
|
-
}
|
|
8640
8342
|
const lookup = await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot, company);
|
|
8641
8343
|
if (lookup.errors.length > 0) {
|
|
8642
8344
|
throw new LanesError("outpost_question_lookup_unavailable", `Could not verify whether Outposts own question ${questionId}`, {
|
|
@@ -8712,22 +8414,6 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
|
|
|
8712
8414
|
throw new LanesError("missing_choice", "--choice or --freeform is required", {});
|
|
8713
8415
|
}
|
|
8714
8416
|
const choice = freeform ?? resolveAnswerChoice(opts.choice, body.options, false);
|
|
8715
|
-
let remoteAnswer;
|
|
8716
|
-
let meshQuestionId = question.mesh_question_id;
|
|
8717
|
-
if (await ctx.laneTrackerMeshEnabled(questionCompany)) {
|
|
8718
|
-
const client = createTrackerMeshQuestionClient({ company: questionCompany, request: ctx.trackerMeshRequest });
|
|
8719
|
-
if (!meshQuestionId) {
|
|
8720
|
-
const indexed = await indexTrackerQuestion(ctx, hqRoot, askingLane, question, body, questionCompany, client);
|
|
8721
|
-
meshQuestionId = indexed.mesh_question_id;
|
|
8722
|
-
}
|
|
8723
|
-
if (!meshQuestionId)
|
|
8724
|
-
throw new LanesError("tracker_mesh_question_link_missing", "Work Mesh question could not be linked before answering", { question_id: question.question_id });
|
|
8725
|
-
remoteAnswer = await client.answer(meshQuestionId, choice, opts.notes);
|
|
8726
|
-
assertMeshQuestionMatchesLocal(remoteAnswer, question, questionCompany);
|
|
8727
|
-
if (remoteAnswer.status !== "answered" || remoteAnswer.choice !== choice) {
|
|
8728
|
-
throw new LanesError("tracker_mesh_response_invalid", "Work Mesh did not record the requested question answer", { mesh_question_id: meshQuestionId });
|
|
8729
|
-
}
|
|
8730
|
-
}
|
|
8731
8417
|
const at = ctx.now().toISOString();
|
|
8732
8418
|
const answeredBy = sessionIdFromEnv(ctx.env)
|
|
8733
8419
|
? `session:${sessionIdFromEnv(ctx.env)}`
|
|
@@ -8762,7 +8448,6 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
|
|
|
8762
8448
|
answered_at: at,
|
|
8763
8449
|
answered_level: current.hop,
|
|
8764
8450
|
},
|
|
8765
|
-
...(meshQuestionId ? { mesh_question_id: meshQuestionId } : {}),
|
|
8766
8451
|
};
|
|
8767
8452
|
});
|
|
8768
8453
|
const latestAskingLane = readLane(hqRoot, askingLane.lane_id);
|
|
@@ -8797,8 +8482,6 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
|
|
|
8797
8482
|
if (wake.status === "failed") {
|
|
8798
8483
|
throw new LanesError("question_wake_failed", `Question ${questionId} was recorded, but lane ${askingLane.lane_id} could not be woken: ${wake.error}`, { lane_id: askingLane.lane_id, question_id: questionId, wake_error: wake.error });
|
|
8799
8484
|
}
|
|
8800
|
-
if (remoteAnswer)
|
|
8801
|
-
await syncTrackerMeshQuestions(ctx, hqRoot);
|
|
8802
8485
|
printJson({
|
|
8803
8486
|
ok: true,
|
|
8804
8487
|
action: "questions.answer",
|
|
@@ -9430,7 +9113,6 @@ async function runReconcile(ctx, opts) {
|
|
|
9430
9113
|
livenessDeps: livenessDepsFromCtx(ctx),
|
|
9431
9114
|
presenceConnected: ctx.presenceConnected,
|
|
9432
9115
|
resolveQuestionActiveCompany: () => resolveActiveQuestionCompany(ctx),
|
|
9433
|
-
syncTrackerQuestions: (root) => syncTrackerMeshQuestions(ctx, root),
|
|
9434
9116
|
recoverQueuedMessages: async (lane) => {
|
|
9435
9117
|
await recoverQueuedMessages(ctx, lane.lane_id);
|
|
9436
9118
|
},
|
|
@@ -9510,8 +9192,6 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
9510
9192
|
hqRoot: ctx.hqRoot,
|
|
9511
9193
|
resolveCompany: ctx.resolveCompany,
|
|
9512
9194
|
trackerEnabled: ctx.laneTrackerEnabled,
|
|
9513
|
-
trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
|
|
9514
|
-
meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
|
|
9515
9195
|
now: ctx.now,
|
|
9516
9196
|
actor: ctx.trackerActor,
|
|
9517
9197
|
}, wrap);
|
|
@@ -9519,24 +9199,18 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
9519
9199
|
hqRoot: ctx.hqRoot,
|
|
9520
9200
|
resolveCompany: ctx.resolveCompany,
|
|
9521
9201
|
trackerEnabled: ctx.laneTrackerEnabled,
|
|
9522
|
-
trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
|
|
9523
|
-
meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
|
|
9524
9202
|
now: ctx.now,
|
|
9525
9203
|
}, wrap);
|
|
9526
9204
|
registerLaneChecksCommand(lanes, {
|
|
9527
9205
|
hqRoot: ctx.hqRoot,
|
|
9528
9206
|
resolveCompany: ctx.resolveCompany,
|
|
9529
9207
|
trackerEnabled: ctx.laneTrackerEnabled,
|
|
9530
|
-
trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
|
|
9531
|
-
meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
|
|
9532
9208
|
now: ctx.now,
|
|
9533
9209
|
}, wrap);
|
|
9534
9210
|
registerLaneReportCommands(lanes, {
|
|
9535
9211
|
hqRoot: ctx.hqRoot,
|
|
9536
9212
|
resolveCompany: ctx.resolveCompany,
|
|
9537
9213
|
trackerEnabled: ctx.laneTrackerEnabled,
|
|
9538
|
-
trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
|
|
9539
|
-
meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
|
|
9540
9214
|
now: ctx.now,
|
|
9541
9215
|
readQuestionBody: (root, question) => {
|
|
9542
9216
|
const body = requireQuestionBody(root, question);
|
package/dist/lib/bot/config.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export declare function normalizeBotCompanies(values: readonly string[] | undefi
|
|
|
29
29
|
export declare function effectiveBotKind(config: Pick<BotConfig, "kind" | "workerId">): BotKind;
|
|
30
30
|
/** The companies a bot belongs to (empty for a personal bot: its owner's memberships apply). */
|
|
31
31
|
export declare function effectiveBotCompanies(config: Pick<BotConfig, "kind" | "companies" | "workerId">): string[];
|
|
32
|
+
/** Longest display name (Slack's own limit for a bot's display name). */
|
|
33
|
+
export declare const BOT_DISPLAY_NAME_MAX_CHARS = 35;
|
|
32
34
|
/** Longest `intro` (first-start hello DM) a bot may store. */
|
|
33
35
|
export declare const BOT_INTRO_MAX_CHARS = 500;
|
|
34
36
|
/** Longest `kickoff` (first-start model turn prompt) a bot may store. */
|
|
@@ -61,6 +63,11 @@ export declare function validateBotEffort(runtime: BotRuntimeId, value: string):
|
|
|
61
63
|
* it cannot be a model id.
|
|
62
64
|
*/
|
|
63
65
|
export declare function validateBotModel(value: string): string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Trim and check a display name: the human name people see in HQ (the DM list,
|
|
68
|
+
* the bot's own "You are …"). The handle (`name`) stays the CLI's key.
|
|
69
|
+
*/
|
|
70
|
+
export declare function validateBotDisplayName(text: string): string;
|
|
64
71
|
/** Trim and bound the intro text; throws an expected error when unusable. */
|
|
65
72
|
export declare function validateBotIntro(text: string): string;
|
|
66
73
|
/** Trim and bound the kickoff prompt; throws an expected error when unusable. */
|
|
@@ -112,6 +119,11 @@ export interface BotConfig {
|
|
|
112
119
|
* the synced tree (~/.hq/bots/<name>/memory).
|
|
113
120
|
*/
|
|
114
121
|
memoryDir?: string;
|
|
122
|
+
/**
|
|
123
|
+
* The human name people see (e.g. "Maya"). Sent to HQ as the bot's name at
|
|
124
|
+
* create; `name` stays the handle every hq bot command uses. Unset = `name`.
|
|
125
|
+
*/
|
|
126
|
+
displayName?: string;
|
|
115
127
|
/**
|
|
116
128
|
* Verbatim hello the bot DMs its owner when it first comes online (and on
|
|
117
129
|
* `hq bot intro`). Unset = the default introduction.
|
package/dist/lib/bot/config.js
CHANGED
|
@@ -64,6 +64,8 @@ export function effectiveBotCompanies(config) {
|
|
|
64
64
|
}
|
|
65
65
|
return listed;
|
|
66
66
|
}
|
|
67
|
+
/** Longest display name (Slack's own limit for a bot's display name). */
|
|
68
|
+
export const BOT_DISPLAY_NAME_MAX_CHARS = 35;
|
|
67
69
|
/** Longest `intro` (first-start hello DM) a bot may store. */
|
|
68
70
|
export const BOT_INTRO_MAX_CHARS = 500;
|
|
69
71
|
/** Longest `kickoff` (first-start model turn prompt) a bot may store. */
|
|
@@ -120,6 +122,29 @@ export function validateBotModel(value) {
|
|
|
120
122
|
}
|
|
121
123
|
return v;
|
|
122
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Trim and check a display name: the human name people see in HQ (the DM list,
|
|
127
|
+
* the bot's own "You are …"). The handle (`name`) stays the CLI's key.
|
|
128
|
+
*/
|
|
129
|
+
export function validateBotDisplayName(text) {
|
|
130
|
+
const trimmed = (text ?? "").replace(/\s+/g, " ").trim();
|
|
131
|
+
if (!trimmed) {
|
|
132
|
+
throw Object.assign(new Error("--display-name needs some text (the name people see, e.g. Maya)."), {
|
|
133
|
+
expected: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (trimmed.length > BOT_DISPLAY_NAME_MAX_CHARS) {
|
|
137
|
+
throw Object.assign(new Error(`--display-name is too long (${trimmed.length} chars); keep it under ${BOT_DISPLAY_NAME_MAX_CHARS}.`), {
|
|
138
|
+
expected: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (!/^[\p{L}\p{M}][\p{L}\p{M} .'-]*$/u.test(trimmed)) {
|
|
142
|
+
throw Object.assign(new Error("--display-name can use letters, spaces, apostrophes, periods and hyphens."), {
|
|
143
|
+
expected: true,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return trimmed;
|
|
147
|
+
}
|
|
123
148
|
/** Trim and bound the intro text; throws an expected error when unusable. */
|
|
124
149
|
export function validateBotIntro(text) {
|
|
125
150
|
const trimmed = (text ?? "").replace(/\r\n/g, "\n").trim();
|
|
@@ -34,13 +34,14 @@ export interface BotLocalConfig {
|
|
|
34
34
|
autoApprove?: boolean;
|
|
35
35
|
/** Only the memory *kind* travels — never an absolute path from another Mac. */
|
|
36
36
|
memory?: BotMemoryMode;
|
|
37
|
+
displayName?: string;
|
|
37
38
|
intro?: string;
|
|
38
39
|
kickoff?: string;
|
|
39
40
|
}
|
|
40
41
|
/** Walks a value and throws when any key looks like it could carry a secret. */
|
|
41
42
|
export declare function assertNoSecretKeys(value: unknown, path?: string): void;
|
|
42
43
|
/** The bot.json fields a localConfig is built from — nothing else is read. */
|
|
43
|
-
export type BotLocalConfigSource = Pick<BotConfig, "runtime" | "workerSource" | "workerId" | "companySlug" | "model" | "effort" | "kind" | "companies" | "autoApprove" | "memoryDir" | "intro" | "kickoff">;
|
|
44
|
+
export type BotLocalConfigSource = Pick<BotConfig, "runtime" | "workerSource" | "workerId" | "companySlug" | "model" | "effort" | "kind" | "companies" | "autoApprove" | "memoryDir" | "displayName" | "intro" | "kickoff">;
|
|
44
45
|
/**
|
|
45
46
|
* The non-secret settings of a bot, for the cloud to keep. Absolute paths
|
|
46
47
|
* (hqRoot, memoryDir, workerDir) are deliberately left out: they belong to the
|
|
@@ -51,6 +51,7 @@ export function buildBotLocalConfig(config) {
|
|
|
51
51
|
...(kind === "company" ? { companies } : {}),
|
|
52
52
|
...(config.autoApprove === false ? { autoApprove: false } : {}),
|
|
53
53
|
memory: botMemoryMode(config),
|
|
54
|
+
...(config.displayName ? { displayName: config.displayName } : {}),
|
|
54
55
|
...(config.intro ? { intro: config.intro } : {}),
|
|
55
56
|
...(config.kickoff ? { kickoff: config.kickoff } : {}),
|
|
56
57
|
};
|
|
@@ -107,6 +108,9 @@ export function parseBotLocalConfig(value) {
|
|
|
107
108
|
const memory = str("memory");
|
|
108
109
|
if (isBotMemoryMode(memory))
|
|
109
110
|
out.memory = memory;
|
|
111
|
+
const displayName = str("displayName");
|
|
112
|
+
if (displayName)
|
|
113
|
+
out.displayName = displayName;
|
|
110
114
|
const intro = str("intro");
|
|
111
115
|
if (intro)
|
|
112
116
|
out.intro = intro;
|
|
@@ -27,6 +27,9 @@ export declare const PROGRESS_COALESCE_MS = 2000;
|
|
|
27
27
|
export declare const PROGRESS_MIN_GAP_MS = 4000;
|
|
28
28
|
export declare const WORKING_NOTICE_MS = 60000;
|
|
29
29
|
export declare const WORKING_NOTICE_TEXT = "Working on it \u2014 I'll reply here when it's done.";
|
|
30
|
+
/** The setup bot's heads-up, sent once when a turn runs past SETUP_WORKING_NOTICE_MS. */
|
|
31
|
+
export declare const SETUP_WORKING_NOTICE_MS = 30000;
|
|
32
|
+
export declare const SETUP_WORKING_NOTICE_TEXT = "This one takes a minute. I'll reply here as soon as it's done.";
|
|
30
33
|
export interface ProgressPosterOptions {
|
|
31
34
|
/** Post one message; resolve true when it was delivered. */
|
|
32
35
|
send: (body: string) => Promise<boolean>;
|
|
@@ -49,6 +52,14 @@ export interface ProgressPosterOptions {
|
|
|
49
52
|
* (when the turn is not silent); `abandon` posts nothing.
|
|
50
53
|
*/
|
|
51
54
|
buffer?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Never post a message the model wrote right before a tool call. Those are
|
|
57
|
+
* the model's notes to itself ("Now write the worker."), not replies: the
|
|
58
|
+
* runtime stream shows them as a text block followed by a tool_use. A
|
|
59
|
+
* message is held until the next event says which it was: a tool call drops
|
|
60
|
+
* it, anything else (another message, the end of the turn) keeps it.
|
|
61
|
+
*/
|
|
62
|
+
dropPreToolMessages?: boolean;
|
|
52
63
|
}
|
|
53
64
|
/** Whitespace-insensitive form used to recognise repeated text. */
|
|
54
65
|
export declare function normalizeForCompare(text: string): string;
|
|
@@ -65,6 +76,9 @@ export declare class ProgressPoster {
|
|
|
65
76
|
private readonly workingNoticeMs;
|
|
66
77
|
private readonly workingNoticeText;
|
|
67
78
|
private readonly buffer;
|
|
79
|
+
private readonly dropPreTool;
|
|
80
|
+
/** dropPreToolMessages: messages waiting to learn whether a tool call follows. */
|
|
81
|
+
private candidates;
|
|
68
82
|
private pending;
|
|
69
83
|
/** In-between questions, held until the turn ends (see the header). */
|
|
70
84
|
private heldQuestions;
|
|
@@ -77,11 +91,22 @@ export declare class ProgressPoster {
|
|
|
77
91
|
private finished;
|
|
78
92
|
private noticeSent;
|
|
79
93
|
private delivered;
|
|
94
|
+
/**
|
|
95
|
+
* Posts handed to the send chain this turn, counted when queued rather than
|
|
96
|
+
* when delivered. The "(no reply)" fallback checks this: a post still in
|
|
97
|
+
* flight when the turn ends has not bumped `delivered` yet, and counting only
|
|
98
|
+
* deliveries posted the placeholder right after a real message.
|
|
99
|
+
*/
|
|
100
|
+
private queued;
|
|
80
101
|
constructor(opts: ProgressPosterOptions);
|
|
81
102
|
/** Arm the one-time "working on it" note. Call when the turn starts. */
|
|
82
103
|
start(): void;
|
|
83
104
|
/** One finished assistant message from the model. */
|
|
84
105
|
message(text: string): void;
|
|
106
|
+
/** The model called a tool: whatever it wrote just before was a note to itself. */
|
|
107
|
+
tool(): void;
|
|
108
|
+
private promoteCandidates;
|
|
109
|
+
private accept;
|
|
85
110
|
/** Posts that actually reached the chat (progress, notice and final). */
|
|
86
111
|
get postedCount(): number;
|
|
87
112
|
/** True once any message from the model was said this turn (not counting the notice). */
|