@amistio/cli 0.1.9 → 0.1.11

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/README.md CHANGED
@@ -13,6 +13,8 @@ The package install only installs the `amistio` command. Repository cloning, pro
13
13
 
14
14
  Runner lifecycle controls in the web app, such as update, restart, and remove, apply only to the runner paired by that user unless the active organization role is an admin role. The runner API binds command polling, command status, logs, activity, and tool sessions to the local runner credential that produced them.
15
15
 
16
+ Runner Update installs the official `@amistio/cli` package and then refreshes the runner runtime. Background runners attempt a replacement restart so the next heartbeat reports the new CLI version. Foreground `amistio run --watch` sessions stop after a successful install with restart guidance; start the command again to load the updated package.
17
+
16
18
  Repository brain auto-sync is disabled until the repository link option is enabled in the app. After pairing, run `amistio sync watch` from the paired checkout to push recognized external brain Markdown/MDX files, including local ADRs, plans, prompts, workflows, memory, context, architecture, and feature docs, to the app for review. `amistio run --watch` also runs the same cycle between work polls when the option is enabled. The CLI skips templates, unsupported paths, oversized files, unchanged managed docs, and conflicts instead of silently overwriting web state.
17
19
 
18
20
  After pairing, confirm that at least one local AI tool is available:
@@ -37,6 +39,8 @@ When `--tool copilot` uses the GitHub Copilot SDK, Amistio approves read-only pe
37
39
 
38
40
  `amistio runner status` reports local background runner state, latest heartbeat, and bounded resource usage when available. Resource usage is latest-sample runner process memory/CPU plus safe aggregate system memory/load signals; it does not include source files, environment variables, command lines, process lists, credentials, or arbitrary local paths.
39
41
 
42
+ The runner advertises its supported work kinds in heartbeats. Current runners can claim read-only issue diagnosis jobs from the web Issues panel, generate root-cause analysis and a proposed fix, and submit that result without modifying source. Implementation is queued separately only after the user approves the analysis in the app.
43
+
40
44
  Runner setup and local-tool execution use bounded failure controls. `amistio run --watch` retries Git worktree preflight failures by releasing the claim for another attempt, then fails the work item after `--max-preflight-attempts` attempts, defaulting to 3. Active local-tool runs renew the work lease, and `--tool-timeout-seconds` caps tool execution, defaulting to 1800 seconds.
41
45
 
42
46
  For headless startup after login on supported user-level service managers:
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ var itemTypeSchema = z.enum([
20
20
  "repositoryLink",
21
21
  "brainDocument",
22
22
  "generatedDraft",
23
+ "issue",
23
24
  "syncCursor",
24
25
  "syncConflict",
25
26
  "workItem",
@@ -66,11 +67,15 @@ var workStatusSchema = z.enum([
66
67
  ]);
67
68
  var sourceSchema = z.enum(["web", "repo", "generated", "runner"]);
68
69
  var executionModeSchema = z.enum(["localRunner", "cloudSandbox"]);
69
- var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview"]);
70
+ var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis"]);
70
71
  var generatedDraftStatusSchema = z.enum(["queued", "generating", "reviewing", "approved", "rejected", "changesRequested", "failed"]);
71
72
  var assistantQuestionModeSchema = z.enum(["brainOnly", "sourceAware"]);
72
73
  var impactRiskLevelSchema = z.enum(["low", "medium", "high", "critical"]);
73
74
  var impactReportStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale"]);
75
+ var issueCategorySchema = z.enum(["bug", "regression", "brokenWorkflow", "security", "operational", "other"]);
76
+ var issueSeveritySchema = z.enum(["low", "medium", "high", "critical"]);
77
+ var issueStatusSchema = z.enum(["queued", "diagnosing", "analysisReady", "approved", "changesRequested", "rejected", "implementationQueued", "implemented", "failed"]);
78
+ var issueApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "rejected", "implemented", "blocked"]);
74
79
  var activityEventTypeSchema = z.enum([
75
80
  "commandSubmitted",
76
81
  "generationQueued",
@@ -89,6 +94,11 @@ var activityEventTypeSchema = z.enum([
89
94
  "impactPreviewCompleted",
90
95
  "impactPreviewFailed",
91
96
  "impactPreviewStale",
97
+ "issueDiagnosisQueued",
98
+ "issueDiagnosisCompleted",
99
+ "issueDiagnosisFailed",
100
+ "issueReview",
101
+ "issueImplementationQueued",
92
102
  "handoffExported"
93
103
  ]);
94
104
  var activityEventActorSchema = z.enum(["webUser", "runner", "cli", "system"]);
@@ -338,6 +348,7 @@ var workItemSchema = baseItemSchema.extend({
338
348
  approvedByUserId: z.string().min(1).optional(),
339
349
  sourceWish: z.string().min(1).optional(),
340
350
  generatedDraftId: z.string().min(1).optional(),
351
+ issueId: z.string().min(1).optional(),
341
352
  repositoryLinkId: z.string().min(1).optional(),
342
353
  reviewThreadId: z.string().min(1).optional(),
343
354
  reviewDocumentId: z.string().min(1).optional(),
@@ -386,6 +397,7 @@ var runnerHeartbeatItemSchema = baseItemSchema.extend({
386
397
  mode: z.enum(["foreground", "background"]).optional(),
387
398
  hostname: z.string().min(1).optional(),
388
399
  runnerName: z.string().min(1).optional(),
400
+ supportedWorkKinds: z.array(workKindSchema).optional(),
389
401
  supportsBranchIsolation: z.boolean().optional(),
390
402
  supportsGitWorktreeIsolation: z.boolean().optional(),
391
403
  currentWorkItemId: z.string().min(1).optional(),
@@ -720,6 +732,47 @@ var impactPreviewResultSchema = z.object({
720
732
  analyzedRepoRevision: z.number().int().nonnegative().optional(),
721
733
  repoFingerprint: z.string().min(1).optional()
722
734
  });
735
+ var issueDiagnosisResultSchema = z.object({
736
+ summary: z.string().trim().min(1),
737
+ impact: z.string().trim().min(1),
738
+ evidence: z.array(z.string().trim().min(1)).default([]),
739
+ affectedSurfaces: z.array(z.string().trim().min(1)).default([]),
740
+ rootCauseAnalysis: z.string().trim().min(1),
741
+ falsifiableHypothesis: z.string().trim().min(1).optional(),
742
+ nextCheck: z.string().trim().min(1).optional(),
743
+ proposedFix: z.string().trim().min(1),
744
+ expectedBehavior: z.string().trim().min(1),
745
+ verificationPlan: z.array(z.string().trim().min(1)).min(1),
746
+ riskLevel: impactRiskLevelSchema.default("medium"),
747
+ likelyPaths: z.array(impactPathSchema).default([]),
748
+ approvalState: issueApprovalStateSchema.default("proposed")
749
+ });
750
+ var issueItemSchema = baseItemSchema.extend({
751
+ type: z.literal("issue"),
752
+ projectId: z.string().min(1),
753
+ issueId: z.string().min(1),
754
+ title: z.string().trim().min(1).max(200),
755
+ description: z.string().trim().min(1).max(12e3),
756
+ category: issueCategorySchema.default("bug"),
757
+ severity: issueSeveritySchema.default("medium"),
758
+ status: issueStatusSchema,
759
+ approvalState: issueApprovalStateSchema.default("proposed"),
760
+ submittedByUserId: z.string().min(1).optional(),
761
+ repositoryLinkId: z.string().min(1).optional(),
762
+ diagnosisWorkItemId: z.string().min(1).optional(),
763
+ implementationWorkItemId: z.string().min(1).optional(),
764
+ diagnosis: issueDiagnosisResultSchema.optional(),
765
+ reviewNotes: z.string().trim().min(1).optional(),
766
+ approvedByUserId: z.string().min(1).optional(),
767
+ approvedAt: isoDateTimeSchema.optional(),
768
+ requestedChangesByUserId: z.string().min(1).optional(),
769
+ requestedChangesAt: isoDateTimeSchema.optional(),
770
+ rejectedByUserId: z.string().min(1).optional(),
771
+ rejectedAt: isoDateTimeSchema.optional(),
772
+ diagnosedAt: isoDateTimeSchema.optional(),
773
+ runnerId: z.string().min(1).optional(),
774
+ lastDiagnosisError: z.string().optional()
775
+ });
723
776
  var activityEventItemSchema = baseItemSchema.extend({
724
777
  type: z.literal("activityEvent"),
725
778
  projectId: z.string().min(1),
@@ -734,6 +787,7 @@ var activityEventItemSchema = baseItemSchema.extend({
734
787
  occurredAt: isoDateTimeSchema,
735
788
  relatedWorkItemId: z.string().min(1).optional(),
736
789
  relatedDocumentId: z.string().min(1).optional(),
790
+ relatedIssueId: z.string().min(1).optional(),
737
791
  generatedDraftId: z.string().min(1).optional(),
738
792
  runnerId: z.string().min(1).optional(),
739
793
  repositoryLinkId: z.string().min(1).optional(),
@@ -790,6 +844,7 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
790
844
  repositoryLinkItemSchema,
791
845
  brainDocumentItemSchema,
792
846
  generatedDraftItemSchema,
847
+ issueItemSchema,
793
848
  syncCursorItemSchema,
794
849
  syncConflictItemSchema,
795
850
  workItemSchema,
@@ -1527,6 +1582,13 @@ var ApiClient = class {
1527
1582
  { method: "GET" }
1528
1583
  );
1529
1584
  }
1585
+ async listIssues(projectId) {
1586
+ return this.request(
1587
+ `/projects/${projectId}/issues`,
1588
+ z3.object({ issues: z3.array(issueItemSchema) }),
1589
+ { method: "GET" }
1590
+ );
1591
+ }
1530
1592
  async pushBrainDocuments(projectId, documents) {
1531
1593
  return this.request(
1532
1594
  `/projects/${projectId}/brain-documents`,
@@ -1684,6 +1746,16 @@ var ApiClient = class {
1684
1746
  }
1685
1747
  );
1686
1748
  }
1749
+ async submitIssueDiagnosisResult(projectId, workItemId, result) {
1750
+ return this.request(
1751
+ `/projects/${projectId}/work-items/${workItemId}/issue-result`,
1752
+ z3.object({ issue: issueItemSchema, workItem: workItemSchema }),
1753
+ {
1754
+ method: "POST",
1755
+ body: JSON.stringify(result)
1756
+ }
1757
+ );
1758
+ }
1687
1759
  async request(urlPath, schema, init) {
1688
1760
  const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
1689
1761
  ...init,
@@ -3652,6 +3724,8 @@ var assistantAnswerStart = "AMISTIO_ASSISTANT_ANSWER_START";
3652
3724
  var assistantAnswerEnd = "AMISTIO_ASSISTANT_ANSWER_END";
3653
3725
  var impactPreviewStart = "AMISTIO_IMPACT_PREVIEW_START";
3654
3726
  var impactPreviewEnd = "AMISTIO_IMPACT_PREVIEW_END";
3727
+ var issueDiagnosisStart = "AMISTIO_ISSUE_DIAGNOSIS_START";
3728
+ var issueDiagnosisEnd = "AMISTIO_ISSUE_DIAGNOSIS_END";
3655
3729
  function createWorkExecutionPrompt(workItem, context) {
3656
3730
  if (workItem.workKind === "brainGeneration") {
3657
3731
  return createBrainGenerationPrompt(workItem);
@@ -3665,6 +3739,9 @@ function createWorkExecutionPrompt(workItem, context) {
3665
3739
  if (workItem.workKind === "impactPreview") {
3666
3740
  return createImpactPreviewPrompt(workItem, context?.impactPreview);
3667
3741
  }
3742
+ if (workItem.workKind === "issueDiagnosis") {
3743
+ return createIssueDiagnosisPrompt(workItem, context?.issueDiagnosis);
3744
+ }
3668
3745
  return [
3669
3746
  "# Amistio Work Execution",
3670
3747
  "",
@@ -3693,6 +3770,56 @@ function createWorkExecutionPrompt(workItem, context) {
3693
3770
  "- Run relevant verification commands when feasible and summarize results."
3694
3771
  ].join("\n");
3695
3772
  }
3773
+ function createIssueDiagnosisPrompt(workItem, context) {
3774
+ const issue = context?.issue;
3775
+ const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
3776
+ `### ${document.title}`,
3777
+ `documentId: ${document.documentId}`,
3778
+ `documentType: ${document.documentType}`,
3779
+ `repoPath: ${document.repoPath}`,
3780
+ `revision: ${document.revision}`,
3781
+ document.content.slice(0, 3e3)
3782
+ ].join("\n")).join("\n\n");
3783
+ return [
3784
+ "# Amistio Issue Diagnosis",
3785
+ "",
3786
+ "You are running locally through the Amistio CLI inside the user's repository.",
3787
+ "Diagnose the submitted bug or operational issue and prepare the approval-gated fix analysis.",
3788
+ "This is a diagnosis-only task. Do not modify files, create branches, run implementation commands, or commit changes.",
3789
+ "",
3790
+ "## Submitted Issue",
3791
+ "",
3792
+ `Issue ID: ${workItem.issueId ?? issue?.issueId ?? "unknown"}`,
3793
+ `Title: ${issue?.title ?? workItem.title}`,
3794
+ `Category: ${issue?.category ?? "bug"}`,
3795
+ `Severity: ${issue?.severity ?? "medium"}`,
3796
+ "",
3797
+ issue?.description ?? workItem.sourceWish ?? workItem.title,
3798
+ "",
3799
+ "## Approved Project Brain Context",
3800
+ "",
3801
+ approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in evidence.",
3802
+ "",
3803
+ "## Analysis Requirements",
3804
+ "",
3805
+ "- Document user-visible impact and concrete evidence or reproduction status.",
3806
+ "- Name affected surfaces and likely paths using concise path summaries only.",
3807
+ "- Provide rootCauseAnalysis. If unconfirmed, include falsifiableHypothesis and nextCheck.",
3808
+ "- Propose the fix and expected behavior, then provide a verification plan.",
3809
+ "- Keep repository source and secrets local. Do not include raw source dumps, credentials, local secret paths, or provider session references.",
3810
+ "- Leave approvalState as proposed. Implementation must wait for user approval in Amistio.",
3811
+ "",
3812
+ "## Output Contract",
3813
+ "",
3814
+ "Print exactly one JSON object between the markers below. The CLI will submit only this structured diagnosis back to Amistio.",
3815
+ "",
3816
+ issueDiagnosisStart,
3817
+ '{"summary":"The issue is caused by a missing state transition in the workspace action flow.","impact":"Users see a submitted action but no follow-up analysis appears.","evidence":["Reproduced by submitting an issue from the workspace UI"],"affectedSurfaces":["Workspace UI","Runner claim API"],"rootCauseAnalysis":"The issue path queues normal implementation work instead of diagnosis work.","falsifiableHypothesis":"Adding issueDiagnosis work and a dedicated result route will produce analysis without source mutation.","nextCheck":"Run focused web and CLI typechecks after wiring the route.","proposedFix":"Add a first-class issue diagnosis work kind, result parser, and approval-gated implementation queue.","expectedBehavior":"Submitted issues show a complete analysis and only queue implementation after approval.","verificationPlan":["Run shared, web, and CLI typechecks","Submit an issue with a compatible runner and verify analysis appears"],"riskLevel":"medium","likelyPaths":[{"repoPath":"src/apps/web/legacy/api/projects/[projectId]/issues.ts","reason":"Issue submission and approval route"}],"approvalState":"proposed"}',
3818
+ issueDiagnosisEnd,
3819
+ "",
3820
+ "Do not put Markdown fences around the markers. Do not implement the fix."
3821
+ ].join("\n");
3822
+ }
3696
3823
  function createImpactPreviewPrompt(workItem, context) {
3697
3824
  const implementationPrompt = context?.implementationPrompt;
3698
3825
  const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
@@ -3869,6 +3996,16 @@ function parseImpactPreviewResult(output) {
3869
3996
  const parsed = JSON.parse(stripJsonFence(payload));
3870
3997
  return impactPreviewResultSchema.parse(parsed);
3871
3998
  }
3999
+ function parseIssueDiagnosisResult(output) {
4000
+ const start = output.indexOf(issueDiagnosisStart);
4001
+ const end = output.indexOf(issueDiagnosisEnd, start + issueDiagnosisStart.length);
4002
+ if (start === -1 || end === -1 || end <= start) {
4003
+ throw new Error("Local AI diagnosis did not return an Amistio issue diagnosis block.");
4004
+ }
4005
+ const payload = output.slice(start + issueDiagnosisStart.length, end).trim();
4006
+ const parsed = JSON.parse(stripJsonFence(payload));
4007
+ return issueDiagnosisResultSchema.parse(parsed);
4008
+ }
3872
4009
  function createBrainGenerationPrompt(workItem) {
3873
4010
  const wish = workItem.sourceWish ?? workItem.title;
3874
4011
  return [
@@ -4412,6 +4549,39 @@ async function runOfficialCliUpdate() {
4412
4549
  }
4413
4550
  return { succeeded: false, message: "Official Amistio CLI update command failed.", error: result.output || `npm exited with code ${result.exitCode}.` };
4414
4551
  }
4552
+ async function runOfficialCliUpdateWithRuntimeRefresh(options) {
4553
+ const updateResult = await (options.runUpdate ?? runOfficialCliUpdate)();
4554
+ if (!updateResult.succeeded) {
4555
+ return updateResult;
4556
+ }
4557
+ if (options.mode === "foreground") {
4558
+ return {
4559
+ succeeded: true,
4560
+ stopRunner: true,
4561
+ message: `${updateResult.message} Restart the local runner command to load the updated CLI version.`
4562
+ };
4563
+ }
4564
+ if (!options.restartBackgroundRunner) {
4565
+ return {
4566
+ succeeded: false,
4567
+ message: `${updateResult.message} Background runner restart was not available after update. Restart the runner manually to load the updated CLI version.`,
4568
+ error: "Background runner restart hook was not provided."
4569
+ };
4570
+ }
4571
+ const restartResult = await options.restartBackgroundRunner();
4572
+ if (restartResult.succeeded) {
4573
+ return {
4574
+ succeeded: true,
4575
+ stopRunner: true,
4576
+ message: `${updateResult.message} ${restartResult.message}`
4577
+ };
4578
+ }
4579
+ return {
4580
+ succeeded: false,
4581
+ message: `${updateResult.message} Background runner restart failed after update. Restart the runner manually to load the updated CLI version.`,
4582
+ error: restartResult.error ?? restartResult.message
4583
+ };
4584
+ }
4415
4585
  function runOfficialUpdateProcess(command, args, timeoutMs) {
4416
4586
  return new Promise((resolve) => {
4417
4587
  const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
@@ -4548,6 +4718,7 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
4548
4718
  var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
4549
4719
  var RUNNER_WORK_LEASE_SECONDS = 300;
4550
4720
  var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
4721
+ var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis"];
4551
4722
  program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
4552
4723
  program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
4553
4724
  const created = await initControlPlane(options.root);
@@ -5510,6 +5681,24 @@ async function runNextWorkItem({
5510
5681
  return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
5511
5682
  }
5512
5683
  }
5684
+ if (result.workItem.workKind === "issueDiagnosis") {
5685
+ try {
5686
+ return await finalizeIssueDiagnosisWork({
5687
+ apiClient,
5688
+ durationMs: Date.now() - startedAt,
5689
+ projectId,
5690
+ repositoryLinkId,
5691
+ runnerId,
5692
+ sessionContext,
5693
+ toolConfig,
5694
+ toolName: preview.toolName,
5695
+ toolResult,
5696
+ workItem: result.workItem
5697
+ });
5698
+ } catch (error) {
5699
+ return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
5700
+ }
5701
+ }
5513
5702
  const finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
5514
5703
  const durationMs = Date.now() - startedAt;
5515
5704
  const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
@@ -5712,6 +5901,7 @@ async function recordRunnerMilestone(apiClient, projectId, workItem, runnerId, r
5712
5901
  repositoryLinkId,
5713
5902
  relatedWorkItemId: workItem.workItemId,
5714
5903
  ...workItem.reviewDocumentId ? { relatedDocumentId: workItem.reviewDocumentId } : workItem.impactDocumentId ? { relatedDocumentId: workItem.impactDocumentId } : {},
5904
+ ...workItem.issueId ? { relatedIssueId: workItem.issueId } : {},
5715
5905
  ...workItem.generatedDraftId ? { generatedDraftId: workItem.generatedDraftId } : {},
5716
5906
  ...input
5717
5907
  }).catch(() => void 0);
@@ -5759,7 +5949,10 @@ async function executeRunnerCommand(command, context) {
5759
5949
  if (command.commandKind === "restart") {
5760
5950
  return restartCurrentRunner(context);
5761
5951
  }
5762
- return runOfficialCliUpdate();
5952
+ return runOfficialCliUpdateWithRuntimeRefresh({
5953
+ mode: currentRunnerMode(),
5954
+ restartBackgroundRunner: () => restartCurrentRunner(context)
5955
+ });
5763
5956
  }
5764
5957
  async function restartCurrentRunner(context) {
5765
5958
  if (currentRunnerMode() !== "background") {
@@ -6047,6 +6240,92 @@ ${toolResult.stderr}`);
6047
6240
  console.error(previewError ?? "Local runner impact preview failed.");
6048
6241
  return { status: "failed", exitCode: toolResult.exitCode || 1 };
6049
6242
  }
6243
+ async function finalizeIssueDiagnosisWork({
6244
+ apiClient,
6245
+ durationMs,
6246
+ projectId,
6247
+ repositoryLinkId,
6248
+ runnerId,
6249
+ sessionContext,
6250
+ toolConfig,
6251
+ toolName,
6252
+ toolResult,
6253
+ workItem
6254
+ }) {
6255
+ let diagnosis = void 0;
6256
+ let diagnosisError;
6257
+ if (toolResult.exitCode === 0) {
6258
+ try {
6259
+ diagnosis = parseIssueDiagnosisResult(`${toolResult.stdout}
6260
+ ${toolResult.stderr}`);
6261
+ } catch (error) {
6262
+ diagnosisError = errorMessage3(error);
6263
+ }
6264
+ } else {
6265
+ diagnosisError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
6266
+ }
6267
+ const finalStatus = diagnosis ? "completed" : "failed";
6268
+ const updatedToolSession = await finalizeToolSession({
6269
+ apiClient,
6270
+ projectId,
6271
+ status: finalStatus,
6272
+ runnerId,
6273
+ workItemId: workItem.workItemId,
6274
+ stdout: toolResult.stdout,
6275
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
6276
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
6277
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
6278
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
6279
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
6280
+ });
6281
+ const sessionTelemetry = {
6282
+ sessionPolicy: sessionContext.policy,
6283
+ sessionDecision: sessionContext.decision,
6284
+ sessionDecisionReason: sessionContext.reason,
6285
+ ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
6286
+ ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
6287
+ };
6288
+ if (diagnosis) {
6289
+ const result = await apiClient.submitIssueDiagnosisResult(projectId, workItem.workItemId, {
6290
+ status: "completed",
6291
+ runnerId,
6292
+ idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
6293
+ diagnosis,
6294
+ tool: toolName,
6295
+ durationMs,
6296
+ ...sessionTelemetry,
6297
+ message: `${toolName} returned an issue root-cause analysis.`
6298
+ });
6299
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
6300
+ status: "completed",
6301
+ summary: `${toolName} returned an issue root-cause analysis.`,
6302
+ idempotencyKey: `runner_milestone_issue_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
6303
+ metadata: { tool: toolName, durationMs, riskLevel: diagnosis.riskLevel, likelyPathCount: diagnosis.likelyPaths.length, verificationSummary: diagnosis.verificationPlan.join(" | ") }
6304
+ });
6305
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
6306
+ console.log("Issue diagnosis returned for approval.");
6307
+ return { status: "completed", exitCode: 0 };
6308
+ }
6309
+ const failedResult = await apiClient.submitIssueDiagnosisResult(projectId, workItem.workItemId, {
6310
+ status: "failed",
6311
+ runnerId,
6312
+ idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
6313
+ tool: toolName,
6314
+ durationMs,
6315
+ ...sessionTelemetry,
6316
+ message: `${toolName} did not produce a valid issue diagnosis.`,
6317
+ ...diagnosisError ? { error: diagnosisError } : {}
6318
+ });
6319
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
6320
+ status: "failed",
6321
+ summary: diagnosisError ?? `${toolName} did not produce a valid issue diagnosis.`,
6322
+ idempotencyKey: `runner_milestone_issue_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
6323
+ metadata: { tool: toolName, durationMs, verificationSummary: "Issue diagnosis output did not include valid structured JSON." }
6324
+ });
6325
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
6326
+ console.error(diagnosisError ?? "Local runner issue diagnosis failed.");
6327
+ return { status: "failed", exitCode: toolResult.exitCode || 1 };
6328
+ }
6050
6329
  async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
6051
6330
  if (workItem.workKind === "assistantQuestion") {
6052
6331
  const [{ documents: documents2 }, { messages: messages2 }] = await Promise.all([
@@ -6068,6 +6347,19 @@ async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
6068
6347
  }
6069
6348
  });
6070
6349
  }
6350
+ if (workItem.workKind === "issueDiagnosis") {
6351
+ const [{ documents: documents2 }, { issues }] = await Promise.all([
6352
+ apiClient.listBrainDocuments(projectId),
6353
+ apiClient.listIssues(projectId)
6354
+ ]);
6355
+ const issue = issues.find((item) => item.issueId === workItem.issueId || item.id === workItem.issueId);
6356
+ return createWorkExecutionPrompt(workItem, {
6357
+ issueDiagnosis: {
6358
+ documents: documents2,
6359
+ ...issue ? { issue } : {}
6360
+ }
6361
+ });
6362
+ }
6071
6363
  if (workItem.workKind !== "planRevision" || !workItem.reviewDocumentId) {
6072
6364
  return createWorkExecutionPrompt(workItem);
6073
6365
  }
@@ -6545,6 +6837,7 @@ function toRunnerToolCapabilities(tools) {
6545
6837
  function runnerIsolationCapabilityMetadata() {
6546
6838
  return {
6547
6839
  machineId: runnerMachineId(),
6840
+ supportedWorkKinds: runnerSupportedWorkKinds,
6548
6841
  supportsBranchIsolation: true,
6549
6842
  supportsGitWorktreeIsolation: true
6550
6843
  };