@owlmeans/viable-sdk 0.1.18-rc.21 → 0.1.18-rc.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/viable-sdk/SKILL.md +15 -15
  4. package/build/api/remote.d.ts +2 -3
  5. package/build/api/remote.d.ts.map +1 -1
  6. package/build/api/remote.js +6 -14
  7. package/build/api/remote.js.map +1 -1
  8. package/build/consts.d.ts +3 -13
  9. package/build/consts.d.ts.map +1 -1
  10. package/build/consts.js +3 -13
  11. package/build/consts.js.map +1 -1
  12. package/build/harness/templates.d.ts +2 -2
  13. package/build/harness/templates.d.ts.map +1 -1
  14. package/build/harness/templates.js +10 -10
  15. package/build/project/setup.d.ts +1 -1
  16. package/build/tools/catalogue.d.ts +2 -2
  17. package/build/tools/catalogue.d.ts.map +1 -1
  18. package/build/tools/catalogue.js +73 -133
  19. package/build/tools/catalogue.js.map +1 -1
  20. package/build/tools/index.d.ts +1 -1
  21. package/build/tools/index.d.ts.map +1 -1
  22. package/build/tools/index.js +1 -1
  23. package/build/tools/index.js.map +1 -1
  24. package/build/tools/mcp.d.ts +2 -2
  25. package/build/tools/mcp.js +10 -10
  26. package/build/tools/mcp.js.map +1 -1
  27. package/build/tools/platform.d.ts +4 -8
  28. package/build/tools/platform.d.ts.map +1 -1
  29. package/build/tools/platform.js +22 -36
  30. package/build/tools/platform.js.map +1 -1
  31. package/build/tools/refusal.d.ts +2 -2
  32. package/build/tools/refusal.js +6 -6
  33. package/build/tools/refusal.js.map +1 -1
  34. package/build/tools/status.d.ts +6 -0
  35. package/build/tools/status.d.ts.map +1 -0
  36. package/build/tools/status.js +102 -0
  37. package/build/tools/status.js.map +1 -0
  38. package/build/types.d.ts +16 -17
  39. package/build/types.d.ts.map +1 -1
  40. package/package.json +19 -19
  41. package/build/tools/jobs.d.ts +0 -12
  42. package/build/tools/jobs.d.ts.map +0 -1
  43. package/build/tools/jobs.js +0 -60
  44. package/build/tools/jobs.js.map +0 -1
@@ -1,16 +1,16 @@
1
1
  import { z } from 'zod';
2
2
  import { CommitTimeout, TransitionAction, WorkcardKind } from '@owlmeans/planning';
3
- import { ConnectJobKind, ConnectTarget, ConversionDecision, ConversionStatus, ConvertibilityVerdict, decisionFor, jobIdOf, MODEL_TIER_ROLES, OriginKind, STORY_BAND_MAX_USD, STORY_BAND_MIN_USD, VIABLE_STORY_TYPE, ViableStoryTransition } from '@owlmeans/viable-common';
4
- import { COMMIT_WAIT_MS, JOB_POLL_MAX_SEC, NEXT_QUESTION_WAIT_MS, NEXT_TASK_WAIT_MS, STORY_PAGE_SIZE } from '../consts.js';
3
+ import { ConnectTarget, ConversionDecision, ConversionStatus, ConvertibilityVerdict, MODEL_TIER_ROLES, OriginKind, STORY_BAND_MAX_USD, STORY_BAND_MIN_USD, VIABLE_STORY_TYPE, ViableStoryTransition } from '@owlmeans/viable-common';
4
+ import { COMMIT_WAIT_MS, NEXT_QUESTION_WAIT_MS, NEXT_TASK_WAIT_MS, STORY_PAGE_SIZE } from '../consts.js';
5
5
  import { describeHarness, installHarness } from '../harness/index.js';
6
6
  import { envStatus } from '../project/env.js';
7
7
  import { missingServices, readSetupReport, renderSetupGuide, setUserEnv } from '../project/setup.js';
8
8
  import { localStatus, runLocal, stopLocal } from '../run/index.js';
9
9
  import { parseTaskResult, renderTaskEnvelope } from '../task/envelope.js';
10
10
  import { parseAnswer, renderQuestionEnvelope } from '../task/inquiry.js';
11
- import { renderJob } from './jobs.js';
12
11
  import { PLATFORM_CATALOGUE, renderPlatform } from './platform.js';
13
12
  import { refusalMessage, refusalPhrase } from './refusal.js';
13
+ import { conversionNext, renderPipelineStatus, renderProjectStatus, renderStoryStatus, } from './status.js';
14
14
  import { renderStories, resolveStory, STORY_ORDER, storyQuery } from './stories.js';
15
15
  import { anyHost, cloudTarget, localTarget, performsModelTasks, sessionCapable, ToolHostKind, withExecutor } from './types.js';
16
16
  const ok = (text, structured) => ({ text, structured });
@@ -72,7 +72,10 @@ const projectOf = (args, deps) => {
72
72
  }
73
73
  return project;
74
74
  };
75
- const jobResult = (job) => ok(renderJob(job), { job: job });
75
+ const projectResult = (status) => ok(renderProjectStatus(status), { project: status });
76
+ const storyResult = (status) => ok(renderStoryStatus(status), { story: status });
77
+ const pipelineResult = (status) => ok(renderPipelineStatus(status), { pipeline: status });
78
+ const conversionResult = (status) => ok(renderConversion(status), { conversion: status });
76
79
  /**
77
80
  * Attach a connector, where this host can hold one.
78
81
  *
@@ -123,36 +126,35 @@ const waitForStoryCleanup = async (deps, projectId) => {
123
126
  /** The project a tool acts on when it can also work without one. */
124
127
  const projectOrNull = (args, deps) => typeof args.projectId === 'string' ? args.projectId : deps.attached();
125
128
  /**
126
- * The question a PARKED run is waiting on, when the connector's own queue holds none.
129
+ * The question a parked domain operation is waiting on, when this session holds none.
127
130
  *
128
131
  * A question is delivered as an operation and answered against it, and that is the fast path. But
129
132
  * an operation expires: a run that asked while nobody was attached, or while this connector was
130
133
  * being restarted, is left `Waiting` with a question no queue here has ever seen. Without this the
131
- * only way back to it is the web application, and a parent polling the job would wait out the
132
- * whole timeout on a question it could have carried in seconds.
134
+ * only way back to it is the web application, and a parent watching only the run would wait out the
135
+ * whole interaction on a question it could have carried in seconds.
133
136
  *
134
137
  * Best-effort on both reads. A project with no conversion answers an error, and failing the call
135
138
  * that asked for a question because the lookup for it failed would be worse than answering "none".
136
139
  */
137
- const parkedQuestion = async (deps, projectId, jobId) => {
140
+ const parkedQuestion = async (deps, projectId) => {
138
141
  try {
139
- const status = await deps.api.convert.status(projectId);
142
+ const status = await deps.api.project.status(projectId);
140
143
  if (status.pendingInquiry != null)
141
144
  return status.pendingInquiry;
142
145
  }
143
146
  catch (e) {
144
- deps.log(`no conversion question for ${projectId}: ${e.message}`);
147
+ deps.log(`no project question for ${projectId}: ${e.message}`);
145
148
  }
146
- if (jobId == null)
147
- return null;
148
149
  try {
149
- const job = await deps.api.project.job(projectId, jobId);
150
- return job.inquiry ?? null;
150
+ const status = await deps.api.convert.status(projectId);
151
+ if (status.pendingInquiry != null)
152
+ return status.pendingInquiry;
151
153
  }
152
154
  catch (e) {
153
- deps.log(`no question on job ${jobId}: ${e.message}`);
154
- return null;
155
+ deps.log(`no conversion question for ${projectId}: ${e.message}`);
155
156
  }
157
+ return null;
156
158
  };
157
159
  const questionResult = (inquiry, deps, note) => ok((note != null ? `${note}\n\n` : '') + renderQuestionEnvelope(inquiry, { harness: deps.host.harness }), { questionId: inquiry.id });
158
160
  /** A cost estimate, always with the sentence that says what it is not. */
@@ -237,7 +239,7 @@ const renderCheck = (check, conversion) => {
237
239
  ? 'nothing — this origin cannot be converted'
238
240
  : conversion != null
239
241
  ? conversionNext(conversion)
240
- : 'convert_project to start, then poll the job with wait_for'}`);
242
+ : 'convert_project to start, then read conversion_status'}`);
241
243
  return lines.join('\n');
242
244
  };
243
245
  const renderConversion = (view) => {
@@ -264,47 +266,20 @@ const renderConversion = (view) => {
264
266
  + ` $${STORY_BAND_MIN_USD}–$${STORY_BAND_MAX_USD} each.`);
265
267
  }
266
268
  // Phrased, exactly like a thrown one: the platform writes a failed stage's cause here with
267
- // `describeFailure`, which for a refusal is the marker verbatim — so a conversion stopped by a
268
- // declined relocation answered this tool with `viable-agent-common:conversion:relocate-declined`
269
- // while `wait_for` on the very same run read out the sentence. One refusal, two readings.
269
+ // `describeFailure`, which for a refusal is the marker verbatim. Phrase the stored value the
270
+ // same way as a thrown refusal so every conversion status has one user-facing reading.
270
271
  if (view.lastError != null && view.lastError !== '') {
271
272
  lines.push(`error: ${refusalPhrase(view.lastError)}`);
272
273
  }
273
274
  lines.push(`next: ${conversionNext(view)}`);
274
275
  return lines.join('\n');
275
276
  };
276
- /**
277
- * What to call after reading a conversion.
278
- *
279
- * Derived from the STATUS first and the stage second, because the two say different things: a
280
- * stage says how far the conversion has got, and only the status says whether the platform is
281
- * working, waiting for a person, or waiting for a decision that is the user's.
282
- */
283
- const conversionNext = (view) => {
284
- switch (view.status) {
285
- case ConversionStatus.Running:
286
- return 'wait_for on the job, or conversion_status again';
287
- case ConversionStatus.Waiting:
288
- return 'next_question — this conversion needs a decision from the person you work for';
289
- case ConversionStatus.Awaiting:
290
- return `proceed_conversion { "decision": "${decisionFor(view.stage)}" } to go on, or`
291
- + ` { "decision": "${ConversionDecision.Leave}" } to keep what it has produced`;
292
- case ConversionStatus.Failed:
293
- return `proceed_conversion { "decision": "${ConversionDecision.Retry}" } once the cause is addressed`;
294
- case ConversionStatus.Done:
295
- return 'nothing — this conversion is finished. purge_origin deletes the original sources.';
296
- case ConversionStatus.Cancelled:
297
- return 'nothing — this conversion was cancelled';
298
- default:
299
- return 'convert_project to start it';
300
- }
301
- };
302
277
  /**
303
278
  * Everything a parent agent can ask the platform to do.
304
279
  *
305
280
  * Two rules shape the whole list. Every tool answers inside the strictest host's per-tool ceiling,
306
- * so anything that takes minutes returns a JOB at once and is polled — a coding agent that blocks
307
- * for twenty minutes is reported to its user as a hung server. And every tool that cannot work in
281
+ * so anything that takes minutes returns its current domain status and continues server-side. And
282
+ * every tool that cannot work in
308
283
  * a given mode is HIDDEN rather than failing, so the catalogue a parent reads is exactly the set
309
284
  * of things that work for it.
310
285
  */
@@ -364,12 +339,12 @@ export const catalogue = [
364
339
  // a delegated session performs all of them, an ordinary one performs a conversion's. Both
365
340
  // collect them the same way, so both are told to call next_task.
366
341
  + (performsModelTasks(deps.host)
367
- ? '\n\nThis session runs the platform\'s model calls on YOUR side. Whenever a job reports'
368
- + ' "blocked on: model-task", call next_task.'
342
+ ? '\n\nThis session runs the platform\'s model calls on YOUR side. Whenever a domain status'
343
+ + ' reports waiting for a model task, call next_task.'
369
344
  : sessionCapable(deps.host)
370
345
  ? '\n\nThe platform performs its own model calls for stories and free flight. A'
371
- + ' CONVERSION\'s calls are yours by default. Whenever a job reports "blocked on:'
372
- + ' model-task", call next_task.'
346
+ + ' CONVERSION\'s calls are yours by default. Whenever a domain status reports'
347
+ + ' waiting for a model task, call next_task.'
373
348
  : ''), { tiers, subagents, effortControl, roles: byTier });
374
349
  },
375
350
  },
@@ -438,36 +413,17 @@ export const catalogue = [
438
413
  return ok(lines.join('\n'));
439
414
  },
440
415
  },
441
- {
442
- name: 'wait_for',
443
- title: 'Wait for a job',
444
- description: `Poll one job for up to ${JOB_POLL_MAX_SEC} seconds. Returns as soon as it finishes or`
445
- + ' becomes blocked; call again while it is still running.',
446
- input: {
447
- jobId: z.string(),
448
- projectId: z.string().optional(),
449
- maxWaitSec: z.number().int().min(0).max(JOB_POLL_MAX_SEC).optional(),
450
- },
451
- availability: anyHost,
452
- run: async (args, deps) => {
453
- const project = projectOf(args, deps);
454
- const wait = Math.min(args.maxWaitSec ?? JOB_POLL_MAX_SEC, JOB_POLL_MAX_SEC);
455
- const job = await deps.api.project.job(project, args.jobId, wait);
456
- return jobResult(job);
457
- },
458
- },
459
416
  {
460
417
  name: 'create_project',
461
418
  title: 'Start a project from a description',
462
419
  description: 'Describe the application in a sentence or two. The platform writes a specification, names it'
463
- + ' and drafts a vision. Returns a job — poll it with wait_for, then read the draft and call'
464
- + ' confirm_project.',
420
+ + ' and drafts a vision. Returns the project status; read the draft and call confirm_project.',
465
421
  input: { prompt: z.string().min(1) },
466
422
  availability: anyHost,
467
423
  run: async (args, deps) => {
468
- const job = await deps.api.project.create(args.prompt, deps.host.target === ConnectTarget.Local ? ConnectTarget.Local : ConnectTarget.Cloud);
469
- deps.attach(job.projectId);
470
- return jobResult(job);
424
+ const status = await deps.api.project.create(args.prompt, deps.host.target === ConnectTarget.Local ? ConnectTarget.Local : ConnectTarget.Cloud);
425
+ deps.attach(status.project.id);
426
+ return projectResult(status);
471
427
  },
472
428
  },
473
429
  {
@@ -475,7 +431,7 @@ export const catalogue = [
475
431
  title: 'Accept the specification and build the application',
476
432
  description: 'Confirm the drafted project, optionally editing what the analysis produced. This is what'
477
433
  + ' starts the build: the template lands, dependencies install, the whole application is'
478
- + ' drawn. Returns a job — it takes several minutes.',
434
+ + ' drawn. Returns the project status while the server-side run continues.',
479
435
  input: {
480
436
  projectId: z.string().optional(),
481
437
  name: z.string().optional(),
@@ -492,14 +448,14 @@ export const catalogue = [
492
448
  // whose connector has not filed a session yet is not queued for later — a local target
493
449
  // answers it with `ConnectSessionGone` and the step fails.
494
450
  await ensureSession(deps, project);
495
- const job = await deps.api.project.confirm(project, {
451
+ const status = await deps.api.project.confirm(project, {
496
452
  ...(typeof args.name === 'string' ? { name: args.name } : {}),
497
453
  ...(typeof args.description === 'string' ? { description: args.description } : {}),
498
454
  ...(typeof args.specification === 'string' ? { specification: args.specification } : {}),
499
455
  ...(typeof args.vision === 'string' ? { vision: args.vision } : {}),
500
456
  ...(typeof args.designSystem === 'string' ? { designSystem: args.designSystem } : {}),
501
457
  });
502
- return jobResult(job);
458
+ return projectResult(status);
503
459
  },
504
460
  },
505
461
  {
@@ -598,8 +554,7 @@ export const catalogue = [
598
554
  run: async (args, deps) => {
599
555
  const project = projectOf(args, deps);
600
556
  await ensureSession(deps, project);
601
- const job = await deps.api.project.reinit(project);
602
- return jobResult(job);
557
+ return projectResult(await deps.api.project.reinit(project));
603
558
  },
604
559
  },
605
560
  {
@@ -656,7 +611,7 @@ export const catalogue = [
656
611
  const project = projectOf(args, deps);
657
612
  // Formatting reads the target's file-backed entities before it persists the story. A local
658
613
  // project therefore needs its connector back after an MCP restart even though this tool does
659
- // not itself return a long-running job.
614
+ // not itself start a long-running operation.
660
615
  await ensureSession(deps, project);
661
616
  // The narrative goes as written and with no area: rewriting it and deciding the area is the
662
617
  // platform's, done on the way in for a person's story — a connector that guessed an area
@@ -724,7 +679,7 @@ export const catalogue = [
724
679
  name: 'develop_story',
725
680
  title: 'Implement a user story',
726
681
  description: 'Ask the platform to design and build one story: its screens, its data, its endpoints, its'
727
- + ' navigation. This is the main event and takes many minutes. Returns a job.',
682
+ + ' navigation. This is the main event and takes many minutes. Returns the story status.',
728
683
  input: { storyId: z.string(), projectId: z.string().optional() },
729
684
  availability: anyHost,
730
685
  run: async (args, deps) => {
@@ -745,40 +700,37 @@ export const catalogue = [
745
700
  catch (e) {
746
701
  if (!(e instanceof CommitTimeout))
747
702
  throw e;
748
- // The move is durable and only its commit is late, so the job row is still the answer —
703
+ // The move is durable and only its commit is late, so the story status is still the answer —
749
704
  // waiting out the host's ceiling here would report a slow platform as a broken server.
750
705
  deps.log(`develop_story: the start of ${card.code ?? card.id} has not committed yet;`
751
- + ' answering from the job');
706
+ + ' answering from story status');
752
707
  }
753
- return jobResult(await deps.api.project.job(project, jobIdOf(ConnectJobKind.StoryDevelop, project, card.id)));
708
+ return storyResult(await deps.api.story.status(project, card.id));
754
709
  },
755
710
  },
756
711
  {
757
712
  name: 'story_status',
758
713
  title: 'What happened to a story',
759
- description: 'One story: its status, and the warning explaining a failure.',
714
+ description: 'One story, its development run, and any warning explaining a failure.',
760
715
  input: { storyId: z.string(), projectId: z.string().optional() },
761
716
  availability: anyHost,
762
717
  run: async (args, deps) => {
763
- const card = await resolveStory(deps, projectOf(args, deps), args.storyId);
764
- // A warning is cleared by writing it empty, so an empty one is no warning at all.
765
- const warning = card.fields?.warning;
766
- return ok(`${card.code ?? card.id} · ${card.status}`
767
- + (warning != null && warning !== '' ? `\nwarning: ${String(warning)}` : ''), card);
718
+ const project = projectOf(args, deps);
719
+ const card = await resolveStory(deps, project, args.storyId);
720
+ return storyResult(await deps.api.story.status(project, card.id));
768
721
  },
769
722
  },
770
723
  {
771
724
  name: 'modify_project',
772
725
  title: 'Ask the agent for an open-ended change',
773
726
  description: 'Describe a change in words and let the platform\'s own coding agent make it. For anything'
774
- + ' that is not a user story: a rename, a fix, a styling change. Returns a job.',
727
+ + ' that is not a user story: a rename, a fix, a styling change. Returns project status.',
775
728
  input: { prompt: z.string().min(1), projectId: z.string().optional() },
776
729
  availability: anyHost,
777
730
  run: async (args, deps) => {
778
731
  const project = projectOf(args, deps);
779
732
  await ensureSession(deps, project);
780
- const job = await deps.api.project.modify(project, args.prompt);
781
- return jobResult(job);
733
+ return projectResult(await deps.api.project.modify(project, args.prompt));
782
734
  },
783
735
  },
784
736
  {
@@ -789,10 +741,7 @@ export const catalogue = [
789
741
  availability: anyHost,
790
742
  run: async (args, deps) => {
791
743
  const state = await deps.api.pipeline.state(projectOf(args, deps), args.runId);
792
- return ok(`${String(state.pipeline)} · ${String(state.status)}`
793
- + (state.step != null ? ` · stopped at ${String(state.step)}` : '')
794
- // A run stops on the same refusals a call is thrown, and the row keeps the cause as text.
795
- + (state.error != null ? `\nerror: ${refusalPhrase(String(state.error))}` : ''), state);
744
+ return pipelineResult(state);
796
745
  },
797
746
  },
798
747
  {
@@ -809,10 +758,10 @@ export const catalogue = [
809
758
  run: async (args, deps) => {
810
759
  const project = projectOf(args, deps);
811
760
  await ensureSession(deps, project);
812
- const job = await deps.api.pipeline.resume(project, args.runId, {
761
+ const status = await deps.api.pipeline.resume(project, args.runId, {
813
762
  ...(typeof args.from === 'string' ? { from: args.from } : {}),
814
763
  });
815
- return jobResult(job);
764
+ return pipelineResult(status);
816
765
  },
817
766
  },
818
767
  {
@@ -820,8 +769,8 @@ export const catalogue = [
820
769
  title: 'Get the next model call to perform',
821
770
  description: 'The platform hands you model calls to perform: a conversion\'s by default, and everything'
822
771
  + ' else when this session runs in the delegated mode. Returns one task with instructions for'
823
- + ' running it in a clean subagent, or says there is nothing yet. Call it whenever a job'
824
- + ' reports "blocked on: model-task", and keep calling until it says none.',
772
+ + ' running it in a clean subagent, or says there is nothing yet. Call it whenever a domain'
773
+ + ' status says it is waiting for a model task, and keep calling until it says none.',
825
774
  input: {
826
775
  maxWaitSec: z.number().int().min(0).max(45).optional(),
827
776
  taskId: z.string().optional()
@@ -837,7 +786,7 @@ export const catalogue = [
837
786
  // Asking for one by id re-reads it rather than taking a new one. A task is handed out once
838
787
  // and the platform waits on it for up to 45 minutes, so a parent that lost the envelope —
839
788
  // a compacted conversation, a subagent that died before answering — otherwise has no way
840
- // back to it and the job blocks until the deadline for no reason.
789
+ // back to it and the run waits until the deadline for no reason.
841
790
  if (typeof args.taskId === 'string') {
842
791
  const known = session.taskById(args.taskId);
843
792
  if (known == null) {
@@ -856,8 +805,8 @@ export const catalogue = [
856
805
  + ` ${outstanding.map(one => one.id).join(', ')}. Submit the answer with`
857
806
  + ' submit_task_result, or call next_task with that taskId to read it again.');
858
807
  }
859
- return ok('No task right now. If a job is still running, call wait_for; if it reported'
860
- + ' "blocked on: model-task", call next_task again.');
808
+ return ok('No task right now. Read the matching project, story, conversion, or pipeline status;'
809
+ + ' call next_task again only while it reports waiting for a model task.');
861
810
  }
862
811
  return ok(renderTaskEnvelope(task, { harness: deps.host.harness }), { taskId: task.id });
863
812
  },
@@ -889,20 +838,18 @@ export const catalogue = [
889
838
  const next = session.pendingTasks();
890
839
  return ok(next > 0
891
840
  ? `Accepted. ${next} more task(s) waiting — call next_task.`
892
- : 'Accepted. Call wait_for on the job, or next_task if it is still blocked.');
841
+ : 'Accepted. Read the matching project, story, conversion, or pipeline status.');
893
842
  },
894
843
  },
895
844
  {
896
845
  name: 'next_question',
897
846
  title: 'Get the question the platform is asking',
898
847
  description: 'The platform sometimes needs a decision only the person you are working for can make.'
899
- + ' Returns one question to put to them, or says there is none. Call it whenever a job'
900
- + ' reports "blocked on: question".',
848
+ + ' Returns one question to put to them, or says there is none. Domain status responses also'
849
+ + ' include parked questions directly.',
901
850
  input: {
902
851
  maxWaitSec: z.number().int().min(0).max(45).optional(),
903
852
  questionId: z.string().optional().describe('Re-read a question you were already given.'),
904
- jobId: z.string().optional()
905
- .describe('The job that reported "blocked on: question", if you have its id.'),
906
853
  projectId: z.string().optional(),
907
854
  },
908
855
  // Not `performsModelTasks`: a question has nothing to do with who performs the model calls,
@@ -912,12 +859,11 @@ export const catalogue = [
912
859
  run: async (args, deps) => {
913
860
  const session = await deps.session();
914
861
  const project = projectOrNull(args, deps);
915
- const jobId = typeof args.jobId === 'string' ? args.jobId : undefined;
916
862
  // Asking for one by id re-reads it rather than taking a new one — the same reason
917
863
  // `next_task` does: a parent that lost the envelope has no other way back to it.
918
864
  if (typeof args.questionId === 'string') {
919
865
  const known = session.questionById(args.questionId)
920
- ?? (project != null ? await parkedQuestion(deps, project, jobId) : null);
866
+ ?? (project != null ? await parkedQuestion(deps, project) : null);
921
867
  if (known == null || known.id !== args.questionId) {
922
868
  return fail(`No question ${args.questionId} is waiting. Call next_question with no id.`);
923
869
  }
@@ -935,12 +881,12 @@ export const catalogue = [
935
881
  }
936
882
  // Nothing queued here, which does not mean nothing is being asked: a run that parked while
937
883
  // this connector was away is waiting on a question whose operation has long expired.
938
- const parked = project != null ? await parkedQuestion(deps, project, jobId) : null;
884
+ const parked = project != null ? await parkedQuestion(deps, project) : null;
939
885
  if (parked != null) {
940
886
  return questionResult(parked, deps, 'This run parked on a question before this session attached. It is still open:');
941
887
  }
942
- return ok('No question right now. If a job is still running, call wait_for; if it reported'
943
- + ' "blocked on: question", call next_question again.');
888
+ return ok('No question right now. Read the matching domain status and answer any pendingInquiry it'
889
+ + ' returns; call next_question again only while it reports waiting for a person.');
944
890
  },
945
891
  },
946
892
  {
@@ -954,11 +900,6 @@ export const catalogue = [
954
900
  answer: z.union([z.string(), z.array(z.string())]).optional(),
955
901
  text: z.string().optional().describe('What they said, where the question takes words.'),
956
902
  declined: z.boolean().optional().describe('Nobody could decide this.'),
957
- // Taken here for the same reason `next_question` takes it, and it has to be the SAME reach:
958
- // a question this tool cannot find is refused after a person has already answered it, and
959
- // the parent is sent back to `next_question`, which would offer it again through the job.
960
- jobId: z.string().optional()
961
- .describe('The job that reported "blocked on: question", if you have its id.'),
962
903
  projectId: z.string().optional(),
963
904
  },
964
905
  availability: sessionCapable,
@@ -966,11 +907,10 @@ export const catalogue = [
966
907
  const session = await deps.session();
967
908
  const questionId = args.questionId;
968
909
  const project = projectOrNull(args, deps);
969
- const jobId = typeof args.jobId === 'string' ? args.jobId : undefined;
970
910
  // The question this session is holding, if it still is. That is what decides HOW the answer
971
911
  // travels: an operation the platform is waiting on, or the question's own id.
972
912
  const held = session.questionById(questionId);
973
- const inquiry = held ?? (project != null ? await parkedQuestion(deps, project, jobId) : null);
913
+ const inquiry = held ?? (project != null ? await parkedQuestion(deps, project) : null);
974
914
  if (inquiry == null || inquiry.id !== questionId) {
975
915
  return fail(`No question ${questionId} is waiting. Call next_question for the current one.`);
976
916
  }
@@ -989,7 +929,7 @@ export const catalogue = [
989
929
  }
990
930
  await deps.api.inquiry.answer(project, questionId, answer);
991
931
  }
992
- return ok('Recorded. Call wait_for on the job.');
932
+ return ok('Recorded. Read the matching project, story, conversion, or pipeline status.');
993
933
  },
994
934
  },
995
935
  {
@@ -1018,7 +958,7 @@ export const catalogue = [
1018
958
  title: 'Convert an application you already have',
1019
959
  description: 'Bring an existing application onto the platform: it reads the code, restores the'
1020
960
  + ' specification and the user stories nobody wrote down, and rebuilds it on the platform\'s'
1021
- + ' stack. The original is kept beside it. Returns a job, and stops at your decision after'
961
+ + ' stack. The original is kept beside it. Returns conversion status and stops at your decision after'
1022
962
  + ' each stage.',
1023
963
  input: {
1024
964
  projectId: z.string().optional().describe('Convert into a project that already exists.'),
@@ -1033,7 +973,7 @@ export const catalogue = [
1033
973
  const named = typeof args.projectId === 'string' ? args.projectId : deps.attached();
1034
974
  if (named != null) {
1035
975
  await ensureSession(deps, named);
1036
- return jobResult(await deps.api.convert.start(named));
976
+ return conversionResult(await deps.api.convert.start(named));
1037
977
  }
1038
978
  const repoUrl = typeof args.repoUrl === 'string' && args.repoUrl.trim() !== ''
1039
979
  ? args.repoUrl.trim()
@@ -1042,7 +982,7 @@ export const catalogue = [
1042
982
  return fail('Nothing to convert. Name a project, attach one, run the connector in the directory you'
1043
983
  + ' want converted, or give a repoUrl.');
1044
984
  }
1045
- const job = await deps.api.convert.create({
985
+ const status = await deps.api.convert.create({
1046
986
  ...(typeof args.name === 'string' ? { name: args.name } : {}),
1047
987
  ...(typeof args.about === 'string' ? { about: args.about } : {}),
1048
988
  // A NAMED repository outranks the directory this connector runs in. A stdio connector
@@ -1062,9 +1002,9 @@ export const catalogue = [
1062
1002
  },
1063
1003
  }),
1064
1004
  });
1065
- deps.attach(job.projectId);
1066
- await ensureSession(deps, job.projectId);
1067
- return jobResult(await deps.api.convert.start(job.projectId));
1005
+ deps.attach(status.projectId);
1006
+ await ensureSession(deps, status.projectId);
1007
+ return conversionResult(await deps.api.convert.start(status.projectId));
1068
1008
  }),
1069
1009
  },
1070
1010
  {
@@ -1072,7 +1012,7 @@ export const catalogue = [
1072
1012
  title: 'Decide what a conversion does next',
1073
1013
  description: 'A conversion stops after each stage and waits for a decision: analyze what was read,'
1074
1014
  + ' extract the user stories, implement them, leave it as it stands, retry a stage that'
1075
- + ' failed, or cancel. Returns a job for anything that runs.',
1015
+ + ' failed, or cancel. Returns the conversion status.',
1076
1016
  input: {
1077
1017
  decision: z.enum(Object.values(ConversionDecision)),
1078
1018
  projectId: z.string().optional(),
@@ -1082,8 +1022,8 @@ export const catalogue = [
1082
1022
  run: async (args, deps) => await answering(deps, 'proceed_conversion', async () => {
1083
1023
  const project = projectOf(args, deps);
1084
1024
  await ensureSession(deps, project);
1085
- const job = await deps.api.convert.proceed(project, args.decision, typeof args.note === 'string' ? args.note : undefined);
1086
- return jobResult(job);
1025
+ const status = await deps.api.convert.proceed(project, args.decision, typeof args.note === 'string' ? args.note : undefined);
1026
+ return conversionResult(status);
1087
1027
  }),
1088
1028
  },
1089
1029
  {
@@ -1117,7 +1057,7 @@ export const catalogue = [
1117
1057
  }
1118
1058
  const project = projectOf(args, deps);
1119
1059
  await ensureSession(deps, project);
1120
- return jobResult(await deps.api.convert.purge(project));
1060
+ return conversionResult(await deps.api.convert.purge(project));
1121
1061
  }),
1122
1062
  },
1123
1063
  {