@owlmeans/viable-sdk 0.1.18-rc.21 → 0.1.18-rc.23
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 +1 -1
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/viable-sdk/SKILL.md +76 -21
- package/build/api/remote.d.ts +2 -3
- package/build/api/remote.d.ts.map +1 -1
- package/build/api/remote.js +13 -14
- package/build/api/remote.js.map +1 -1
- package/build/consts.d.ts +3 -13
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +3 -13
- package/build/consts.js.map +1 -1
- package/build/harness/templates.d.ts +2 -2
- package/build/harness/templates.d.ts.map +1 -1
- package/build/harness/templates.js +10 -10
- package/build/project/setup.d.ts +1 -1
- package/build/tools/catalogue.d.ts +2 -2
- package/build/tools/catalogue.d.ts.map +1 -1
- package/build/tools/catalogue.js +143 -142
- package/build/tools/catalogue.js.map +1 -1
- package/build/tools/index.d.ts +2 -1
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js +2 -1
- package/build/tools/index.js.map +1 -1
- package/build/tools/mcp.d.ts +2 -2
- package/build/tools/mcp.d.ts.map +1 -1
- package/build/tools/mcp.js +15 -10
- package/build/tools/mcp.js.map +1 -1
- package/build/tools/platform.d.ts +27 -8
- package/build/tools/platform.d.ts.map +1 -1
- package/build/tools/platform.js +107 -42
- package/build/tools/platform.js.map +1 -1
- package/build/tools/refusal.d.ts +2 -2
- package/build/tools/refusal.d.ts.map +1 -1
- package/build/tools/refusal.js +22 -6
- package/build/tools/refusal.js.map +1 -1
- package/build/tools/settings.d.ts +45 -0
- package/build/tools/settings.d.ts.map +1 -0
- package/build/tools/settings.js +86 -0
- package/build/tools/settings.js.map +1 -0
- package/build/tools/status.d.ts +17 -0
- package/build/tools/status.d.ts.map +1 -0
- package/build/tools/status.js +104 -0
- package/build/tools/status.js.map +1 -0
- package/build/tools/stories.d.ts +19 -1
- package/build/tools/stories.d.ts.map +1 -1
- package/build/tools/stories.js +22 -1
- package/build/tools/stories.js.map +1 -1
- package/build/types.d.ts +34 -17
- package/build/types.d.ts.map +1 -1
- package/package.json +19 -19
- package/build/tools/jobs.d.ts +0 -12
- package/build/tools/jobs.d.ts.map +0 -1
- package/build/tools/jobs.js +0 -60
- package/build/tools/jobs.js.map +0 -1
package/build/tools/catalogue.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { CommitTimeout, TransitionAction, WorkcardKind } from '@owlmeans/planning';
|
|
3
|
-
import {
|
|
4
|
-
import { COMMIT_WAIT_MS,
|
|
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 {
|
|
12
|
-
import { PLATFORM_CATALOGUE, renderPlatform } from './platform.js';
|
|
11
|
+
import { GENERATED_SUMMARY, PLATFORM_CATALOGUE, renderPlatform } from './platform.js';
|
|
13
12
|
import { refusalMessage, refusalPhrase } from './refusal.js';
|
|
14
|
-
import {
|
|
13
|
+
import { renderProjectSettings, settingsPatch, settingsReach } from './settings.js';
|
|
14
|
+
import { conversionNext, renderPipelineStatus, renderProjectStatus, renderStoryStatus, } from './status.js';
|
|
15
|
+
import { isLandingStory, renderStories, resolveStory, STORY_ORDER, storyQuery } from './stories.js';
|
|
15
16
|
import { anyHost, cloudTarget, localTarget, performsModelTasks, sessionCapable, ToolHostKind, withExecutor } from './types.js';
|
|
16
17
|
const ok = (text, structured) => ({ text, structured });
|
|
17
18
|
const fail = (text) => ({ text, isError: true });
|
|
@@ -72,7 +73,16 @@ const projectOf = (args, deps) => {
|
|
|
72
73
|
}
|
|
73
74
|
return project;
|
|
74
75
|
};
|
|
75
|
-
const
|
|
76
|
+
const projectResult = (status) => ok(renderProjectStatus(status), { project: status });
|
|
77
|
+
/** A story's domain status, with what its card says that the status route does not carry. */
|
|
78
|
+
const storyResult = (status, card) => {
|
|
79
|
+
const landing = isLandingStory(card);
|
|
80
|
+
return ok(renderStoryStatus(status, { landing }), { story: status, ...(landing ? { landing } : {}) });
|
|
81
|
+
};
|
|
82
|
+
/** The project settings, led by what a save just did where one did. */
|
|
83
|
+
const settingsResult = (projectId, settings, lead) => ok((lead != null ? `${lead}\n\n` : '') + renderProjectSettings(projectId, settings), { projectId, settings: settings });
|
|
84
|
+
const pipelineResult = (status) => ok(renderPipelineStatus(status), { pipeline: status });
|
|
85
|
+
const conversionResult = (status) => ok(renderConversion(status), { conversion: status });
|
|
76
86
|
/**
|
|
77
87
|
* Attach a connector, where this host can hold one.
|
|
78
88
|
*
|
|
@@ -123,36 +133,35 @@ const waitForStoryCleanup = async (deps, projectId) => {
|
|
|
123
133
|
/** The project a tool acts on when it can also work without one. */
|
|
124
134
|
const projectOrNull = (args, deps) => typeof args.projectId === 'string' ? args.projectId : deps.attached();
|
|
125
135
|
/**
|
|
126
|
-
* The question a
|
|
136
|
+
* The question a parked domain operation is waiting on, when this session holds none.
|
|
127
137
|
*
|
|
128
138
|
* A question is delivered as an operation and answered against it, and that is the fast path. But
|
|
129
139
|
* an operation expires: a run that asked while nobody was attached, or while this connector was
|
|
130
140
|
* 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
|
|
132
|
-
* whole
|
|
141
|
+
* only way back to it is the web application, and a parent watching only the run would wait out the
|
|
142
|
+
* whole interaction on a question it could have carried in seconds.
|
|
133
143
|
*
|
|
134
144
|
* Best-effort on both reads. A project with no conversion answers an error, and failing the call
|
|
135
145
|
* that asked for a question because the lookup for it failed would be worse than answering "none".
|
|
136
146
|
*/
|
|
137
|
-
const parkedQuestion = async (deps, projectId
|
|
147
|
+
const parkedQuestion = async (deps, projectId) => {
|
|
138
148
|
try {
|
|
139
|
-
const status = await deps.api.
|
|
149
|
+
const status = await deps.api.project.status(projectId);
|
|
140
150
|
if (status.pendingInquiry != null)
|
|
141
151
|
return status.pendingInquiry;
|
|
142
152
|
}
|
|
143
153
|
catch (e) {
|
|
144
|
-
deps.log(`no
|
|
154
|
+
deps.log(`no project question for ${projectId}: ${e.message}`);
|
|
145
155
|
}
|
|
146
|
-
if (jobId == null)
|
|
147
|
-
return null;
|
|
148
156
|
try {
|
|
149
|
-
const
|
|
150
|
-
|
|
157
|
+
const status = await deps.api.convert.status(projectId);
|
|
158
|
+
if (status.pendingInquiry != null)
|
|
159
|
+
return status.pendingInquiry;
|
|
151
160
|
}
|
|
152
161
|
catch (e) {
|
|
153
|
-
deps.log(`no question
|
|
154
|
-
return null;
|
|
162
|
+
deps.log(`no conversion question for ${projectId}: ${e.message}`);
|
|
155
163
|
}
|
|
164
|
+
return null;
|
|
156
165
|
};
|
|
157
166
|
const questionResult = (inquiry, deps, note) => ok((note != null ? `${note}\n\n` : '') + renderQuestionEnvelope(inquiry, { harness: deps.host.harness }), { questionId: inquiry.id });
|
|
158
167
|
/** A cost estimate, always with the sentence that says what it is not. */
|
|
@@ -237,7 +246,7 @@ const renderCheck = (check, conversion) => {
|
|
|
237
246
|
? 'nothing — this origin cannot be converted'
|
|
238
247
|
: conversion != null
|
|
239
248
|
? conversionNext(conversion)
|
|
240
|
-
: 'convert_project to start, then
|
|
249
|
+
: 'convert_project to start, then read conversion_status'}`);
|
|
241
250
|
return lines.join('\n');
|
|
242
251
|
};
|
|
243
252
|
const renderConversion = (view) => {
|
|
@@ -264,47 +273,20 @@ const renderConversion = (view) => {
|
|
|
264
273
|
+ ` $${STORY_BAND_MIN_USD}–$${STORY_BAND_MAX_USD} each.`);
|
|
265
274
|
}
|
|
266
275
|
// 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
|
|
268
|
-
//
|
|
269
|
-
// while `wait_for` on the very same run read out the sentence. One refusal, two readings.
|
|
276
|
+
// `describeFailure`, which for a refusal is the marker verbatim. Phrase the stored value the
|
|
277
|
+
// same way as a thrown refusal so every conversion status has one user-facing reading.
|
|
270
278
|
if (view.lastError != null && view.lastError !== '') {
|
|
271
279
|
lines.push(`error: ${refusalPhrase(view.lastError)}`);
|
|
272
280
|
}
|
|
273
281
|
lines.push(`next: ${conversionNext(view)}`);
|
|
274
282
|
return lines.join('\n');
|
|
275
283
|
};
|
|
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
284
|
/**
|
|
303
285
|
* Everything a parent agent can ask the platform to do.
|
|
304
286
|
*
|
|
305
287
|
* Two rules shape the whole list. Every tool answers inside the strictest host's per-tool ceiling,
|
|
306
|
-
* so anything that takes minutes returns
|
|
307
|
-
*
|
|
288
|
+
* so anything that takes minutes returns its current domain status and continues server-side. And
|
|
289
|
+
* every tool that cannot work in
|
|
308
290
|
* a given mode is HIDDEN rather than failing, so the catalogue a parent reads is exactly the set
|
|
309
291
|
* of things that work for it.
|
|
310
292
|
*/
|
|
@@ -313,9 +295,9 @@ export const catalogue = [
|
|
|
313
295
|
name: 'describe_platform',
|
|
314
296
|
title: 'What this platform can build, and what you can drive from here',
|
|
315
297
|
description: 'Everything the platform runs — building an application from a description, implementing'
|
|
316
|
-
+ ' user stories, converting an application you already have —
|
|
317
|
-
+ ' can start. Read it before deciding how to
|
|
318
|
-
+ ' makes no network call.',
|
|
298
|
+
+ ' user stories, converting an application you already have — what every application it'
|
|
299
|
+
+ ' generates carries, and which of it this session can start. Read it before deciding how to'
|
|
300
|
+
+ ' approach a request. Needs no project and makes no network call.',
|
|
319
301
|
input: {},
|
|
320
302
|
availability: anyHost,
|
|
321
303
|
run: async (_args, deps) => ok(renderPlatform(PLATFORM_CATALOGUE, deps.host)),
|
|
@@ -324,7 +306,8 @@ export const catalogue = [
|
|
|
324
306
|
name: 'describe_capabilities',
|
|
325
307
|
title: 'Report what your models can do',
|
|
326
308
|
description: 'Tell the platform which of your models it may use, so it can size each call. Call this once,'
|
|
327
|
-
+ ' before creating or developing anything. Returns the mapping it will use
|
|
309
|
+
+ ' before creating or developing anything. Returns the mapping it will use, and what every'
|
|
310
|
+
+ ' generated application carries.',
|
|
328
311
|
input: {
|
|
329
312
|
strong: z.string().optional().describe('Your most capable model, e.g. the one you plan with'),
|
|
330
313
|
standard: z.string().optional().describe('Your everyday model'),
|
|
@@ -351,7 +334,8 @@ export const catalogue = [
|
|
|
351
334
|
return ok('No model was recorded: this tool takes `strong`, `standard` and `cheap` as TOP-LEVEL'
|
|
352
335
|
+ ' string arguments, not nested under another key. The platform will size every call'
|
|
353
336
|
+ ' with its own defaults until you send them.\n\n'
|
|
354
|
-
+ roleSummary()
|
|
337
|
+
+ roleSummary()
|
|
338
|
+
+ `\n\n${GENERATED_SUMMARY}`, { tiers, roles: rolesByTier() });
|
|
355
339
|
}
|
|
356
340
|
const byTier = rolesByTier();
|
|
357
341
|
return ok('Recorded. The platform asks for three power classes:\n'
|
|
@@ -364,13 +348,14 @@ export const catalogue = [
|
|
|
364
348
|
// a delegated session performs all of them, an ordinary one performs a conversion's. Both
|
|
365
349
|
// collect them the same way, so both are told to call next_task.
|
|
366
350
|
+ (performsModelTasks(deps.host)
|
|
367
|
-
? '\n\nThis session runs the platform\'s model calls on YOUR side. Whenever a
|
|
368
|
-
+ '
|
|
351
|
+
? '\n\nThis session runs the platform\'s model calls on YOUR side. Whenever a domain status'
|
|
352
|
+
+ ' reports waiting for a model task, call next_task.'
|
|
369
353
|
: sessionCapable(deps.host)
|
|
370
354
|
? '\n\nThe platform performs its own model calls for stories and free flight. A'
|
|
371
|
-
+ ' CONVERSION\'s calls are yours by default. Whenever a
|
|
372
|
-
+ ' model
|
|
373
|
-
: '')
|
|
355
|
+
+ ' CONVERSION\'s calls are yours by default. Whenever a domain status reports'
|
|
356
|
+
+ ' waiting for a model task, call next_task.'
|
|
357
|
+
: '')
|
|
358
|
+
+ `\n\n${GENERATED_SUMMARY}`, { tiers, subagents, effortControl, roles: byTier });
|
|
374
359
|
},
|
|
375
360
|
},
|
|
376
361
|
{
|
|
@@ -438,36 +423,17 @@ export const catalogue = [
|
|
|
438
423
|
return ok(lines.join('\n'));
|
|
439
424
|
},
|
|
440
425
|
},
|
|
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
426
|
{
|
|
460
427
|
name: 'create_project',
|
|
461
428
|
title: 'Start a project from a description',
|
|
462
429
|
description: 'Describe the application in a sentence or two. The platform writes a specification, names it'
|
|
463
|
-
+ ' and drafts a vision. Returns
|
|
464
|
-
+ ' confirm_project.',
|
|
430
|
+
+ ' and drafts a vision. Returns the project status; read the draft and call confirm_project.',
|
|
465
431
|
input: { prompt: z.string().min(1) },
|
|
466
432
|
availability: anyHost,
|
|
467
433
|
run: async (args, deps) => {
|
|
468
|
-
const
|
|
469
|
-
deps.attach(
|
|
470
|
-
return
|
|
434
|
+
const status = await deps.api.project.create(args.prompt, deps.host.target === ConnectTarget.Local ? ConnectTarget.Local : ConnectTarget.Cloud);
|
|
435
|
+
deps.attach(status.project.id);
|
|
436
|
+
return projectResult(status);
|
|
471
437
|
},
|
|
472
438
|
},
|
|
473
439
|
{
|
|
@@ -475,7 +441,7 @@ export const catalogue = [
|
|
|
475
441
|
title: 'Accept the specification and build the application',
|
|
476
442
|
description: 'Confirm the drafted project, optionally editing what the analysis produced. This is what'
|
|
477
443
|
+ ' starts the build: the template lands, dependencies install, the whole application is'
|
|
478
|
-
+ ' drawn. Returns
|
|
444
|
+
+ ' drawn. Returns the project status while the server-side run continues.',
|
|
479
445
|
input: {
|
|
480
446
|
projectId: z.string().optional(),
|
|
481
447
|
name: z.string().optional(),
|
|
@@ -492,14 +458,14 @@ export const catalogue = [
|
|
|
492
458
|
// whose connector has not filed a session yet is not queued for later — a local target
|
|
493
459
|
// answers it with `ConnectSessionGone` and the step fails.
|
|
494
460
|
await ensureSession(deps, project);
|
|
495
|
-
const
|
|
461
|
+
const status = await deps.api.project.confirm(project, {
|
|
496
462
|
...(typeof args.name === 'string' ? { name: args.name } : {}),
|
|
497
463
|
...(typeof args.description === 'string' ? { description: args.description } : {}),
|
|
498
464
|
...(typeof args.specification === 'string' ? { specification: args.specification } : {}),
|
|
499
465
|
...(typeof args.vision === 'string' ? { vision: args.vision } : {}),
|
|
500
466
|
...(typeof args.designSystem === 'string' ? { designSystem: args.designSystem } : {}),
|
|
501
467
|
});
|
|
502
|
-
return
|
|
468
|
+
return projectResult(status);
|
|
503
469
|
},
|
|
504
470
|
},
|
|
505
471
|
{
|
|
@@ -598,14 +564,63 @@ export const catalogue = [
|
|
|
598
564
|
run: async (args, deps) => {
|
|
599
565
|
const project = projectOf(args, deps);
|
|
600
566
|
await ensureSession(deps, project);
|
|
601
|
-
|
|
602
|
-
|
|
567
|
+
return projectResult(await deps.api.project.reinit(project));
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: 'project_settings',
|
|
572
|
+
title: 'The project\'s settings',
|
|
573
|
+
description: 'The settings a person edits on the project\'s control panel: the copyright line, the'
|
|
574
|
+
+ ' organization name, the Terms and Privacy links, and the Google tag. A relative link such'
|
|
575
|
+
+ ' as /terms or /privacy is normal — it is the page the platform generated inside the'
|
|
576
|
+
+ ' application, and the generated pages name the organization and copyright set here.',
|
|
577
|
+
input: { projectId: z.string().optional() },
|
|
578
|
+
availability: anyHost,
|
|
579
|
+
run: async (args, deps) => {
|
|
580
|
+
const project = projectOf(args, deps);
|
|
581
|
+
return settingsResult(project, await deps.api.projectBranding(project));
|
|
603
582
|
},
|
|
604
583
|
},
|
|
584
|
+
{
|
|
585
|
+
name: 'update_project_settings',
|
|
586
|
+
title: 'Change the project\'s settings',
|
|
587
|
+
description: 'Change any of the copyright line, the organization name, the Terms and Privacy links and the'
|
|
588
|
+
+ ' Google tag; whatever you leave out keeps its value. Copyright and organization are never'
|
|
589
|
+
+ ' empty. A legal link is an https:// address or a path on the application itself — /terms'
|
|
590
|
+
+ ' and /privacy are the generated pages. A Google tag is a GTM-, G-, GT-, AW- or DC- id, or'
|
|
591
|
+
+ ' an empty string to remove it; it loads on the preview and in production behind the cookie'
|
|
592
|
+
+ ' consent. The preview is rebuilt with the change; production takes it at the next Publish.',
|
|
593
|
+
input: {
|
|
594
|
+
projectId: z.string().optional(),
|
|
595
|
+
copyright: z.string().optional().describe('The copyright line, e.g. "© 2026 Acme Ltd"'),
|
|
596
|
+
organizationName: z.string().optional().describe('Who runs the application'),
|
|
597
|
+
termsUrl: z.string().optional().describe('https://… or a path such as /terms'),
|
|
598
|
+
privacyUrl: z.string().optional().describe('https://… or a path such as /privacy'),
|
|
599
|
+
googleTag: z.string().optional()
|
|
600
|
+
.describe('GTM-XXXXXXX, G-XXXXXXXXXX, GT-…, AW-… or DC-…; an empty string removes it'),
|
|
601
|
+
},
|
|
602
|
+
availability: anyHost,
|
|
603
|
+
run: async (args, deps) => await answering(deps, 'update_project_settings', async () => {
|
|
604
|
+
const project = projectOf(args, deps);
|
|
605
|
+
const patch = settingsPatch(args);
|
|
606
|
+
const changed = Object.keys(patch);
|
|
607
|
+
if (changed.length < 1) {
|
|
608
|
+
return fail('Nothing to change. Give at least one of copyright, organizationName, termsUrl, privacyUrl'
|
|
609
|
+
+ ' or googleTag — project_settings shows the current values.');
|
|
610
|
+
}
|
|
611
|
+
// The save ends in a configuration push, and for a local project that push is an operation
|
|
612
|
+
// THIS connector answers — so it is attached before the platform is asked, exactly as a story
|
|
613
|
+
// mutation is. A host that holds no session skips it and the platform delivers the push itself.
|
|
614
|
+
await ensureSession(deps, project);
|
|
615
|
+
const saved = await deps.api.saveProjectBranding(project, patch);
|
|
616
|
+
return settingsResult(project, saved, `Saved ${changed.join(', ')}. ${settingsReach(deps.host.target)}`);
|
|
617
|
+
}),
|
|
618
|
+
},
|
|
605
619
|
{
|
|
606
620
|
name: 'list_stories',
|
|
607
621
|
title: 'The user stories of a project',
|
|
608
|
-
description: 'One page of the project\'s stories, with their status
|
|
622
|
+
description: 'One page of the project\'s stories, with their status, the primary story, and the landing'
|
|
623
|
+
+ ' gate story a guest starts on the guest home.',
|
|
609
624
|
input: {
|
|
610
625
|
projectId: z.string().optional(),
|
|
611
626
|
page: z.number().int().min(0).optional(),
|
|
@@ -656,7 +671,7 @@ export const catalogue = [
|
|
|
656
671
|
const project = projectOf(args, deps);
|
|
657
672
|
// Formatting reads the target's file-backed entities before it persists the story. A local
|
|
658
673
|
// project therefore needs its connector back after an MCP restart even though this tool does
|
|
659
|
-
// not itself
|
|
674
|
+
// not itself start a long-running operation.
|
|
660
675
|
await ensureSession(deps, project);
|
|
661
676
|
// The narrative goes as written and with no area: rewriting it and deciding the area is the
|
|
662
677
|
// platform's, done on the way in for a person's story — a connector that guessed an area
|
|
@@ -724,7 +739,7 @@ export const catalogue = [
|
|
|
724
739
|
name: 'develop_story',
|
|
725
740
|
title: 'Implement a user story',
|
|
726
741
|
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
|
|
742
|
+
+ ' navigation. This is the main event and takes many minutes. Returns the story status.',
|
|
728
743
|
input: { storyId: z.string(), projectId: z.string().optional() },
|
|
729
744
|
availability: anyHost,
|
|
730
745
|
run: async (args, deps) => {
|
|
@@ -745,40 +760,38 @@ export const catalogue = [
|
|
|
745
760
|
catch (e) {
|
|
746
761
|
if (!(e instanceof CommitTimeout))
|
|
747
762
|
throw e;
|
|
748
|
-
// The move is durable and only its commit is late, so the
|
|
763
|
+
// The move is durable and only its commit is late, so the story status is still the answer —
|
|
749
764
|
// waiting out the host's ceiling here would report a slow platform as a broken server.
|
|
750
765
|
deps.log(`develop_story: the start of ${card.code ?? card.id} has not committed yet;`
|
|
751
|
-
+ ' answering from
|
|
766
|
+
+ ' answering from story status');
|
|
752
767
|
}
|
|
753
|
-
return
|
|
768
|
+
return storyResult(await deps.api.story.status(project, card.id), card);
|
|
754
769
|
},
|
|
755
770
|
},
|
|
756
771
|
{
|
|
757
772
|
name: 'story_status',
|
|
758
773
|
title: 'What happened to a story',
|
|
759
|
-
description: 'One story
|
|
774
|
+
description: 'One story, its development run, any warning explaining a failure, and whether it is the'
|
|
775
|
+
+ ' project\'s landing gate story — the one a guest starts on the guest home.',
|
|
760
776
|
input: { storyId: z.string(), projectId: z.string().optional() },
|
|
761
777
|
availability: anyHost,
|
|
762
778
|
run: async (args, deps) => {
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
return ok(`${card.code ?? card.id} · ${card.status}`
|
|
767
|
-
+ (warning != null && warning !== '' ? `\nwarning: ${String(warning)}` : ''), card);
|
|
779
|
+
const project = projectOf(args, deps);
|
|
780
|
+
const card = await resolveStory(deps, project, args.storyId);
|
|
781
|
+
return storyResult(await deps.api.story.status(project, card.id), card);
|
|
768
782
|
},
|
|
769
783
|
},
|
|
770
784
|
{
|
|
771
785
|
name: 'modify_project',
|
|
772
786
|
title: 'Ask the agent for an open-ended change',
|
|
773
787
|
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
|
|
788
|
+
+ ' that is not a user story: a rename, a fix, a styling change. Returns project status.',
|
|
775
789
|
input: { prompt: z.string().min(1), projectId: z.string().optional() },
|
|
776
790
|
availability: anyHost,
|
|
777
791
|
run: async (args, deps) => {
|
|
778
792
|
const project = projectOf(args, deps);
|
|
779
793
|
await ensureSession(deps, project);
|
|
780
|
-
|
|
781
|
-
return jobResult(job);
|
|
794
|
+
return projectResult(await deps.api.project.modify(project, args.prompt));
|
|
782
795
|
},
|
|
783
796
|
},
|
|
784
797
|
{
|
|
@@ -789,10 +802,7 @@ export const catalogue = [
|
|
|
789
802
|
availability: anyHost,
|
|
790
803
|
run: async (args, deps) => {
|
|
791
804
|
const state = await deps.api.pipeline.state(projectOf(args, deps), args.runId);
|
|
792
|
-
return
|
|
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);
|
|
805
|
+
return pipelineResult(state);
|
|
796
806
|
},
|
|
797
807
|
},
|
|
798
808
|
{
|
|
@@ -809,10 +819,10 @@ export const catalogue = [
|
|
|
809
819
|
run: async (args, deps) => {
|
|
810
820
|
const project = projectOf(args, deps);
|
|
811
821
|
await ensureSession(deps, project);
|
|
812
|
-
const
|
|
822
|
+
const status = await deps.api.pipeline.resume(project, args.runId, {
|
|
813
823
|
...(typeof args.from === 'string' ? { from: args.from } : {}),
|
|
814
824
|
});
|
|
815
|
-
return
|
|
825
|
+
return pipelineResult(status);
|
|
816
826
|
},
|
|
817
827
|
},
|
|
818
828
|
{
|
|
@@ -820,8 +830,8 @@ export const catalogue = [
|
|
|
820
830
|
title: 'Get the next model call to perform',
|
|
821
831
|
description: 'The platform hands you model calls to perform: a conversion\'s by default, and everything'
|
|
822
832
|
+ ' 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
|
|
824
|
-
+ '
|
|
833
|
+
+ ' running it in a clean subagent, or says there is nothing yet. Call it whenever a domain'
|
|
834
|
+
+ ' status says it is waiting for a model task, and keep calling until it says none.',
|
|
825
835
|
input: {
|
|
826
836
|
maxWaitSec: z.number().int().min(0).max(45).optional(),
|
|
827
837
|
taskId: z.string().optional()
|
|
@@ -837,7 +847,7 @@ export const catalogue = [
|
|
|
837
847
|
// Asking for one by id re-reads it rather than taking a new one. A task is handed out once
|
|
838
848
|
// and the platform waits on it for up to 45 minutes, so a parent that lost the envelope —
|
|
839
849
|
// a compacted conversation, a subagent that died before answering — otherwise has no way
|
|
840
|
-
// back to it and the
|
|
850
|
+
// back to it and the run waits until the deadline for no reason.
|
|
841
851
|
if (typeof args.taskId === 'string') {
|
|
842
852
|
const known = session.taskById(args.taskId);
|
|
843
853
|
if (known == null) {
|
|
@@ -856,8 +866,8 @@ export const catalogue = [
|
|
|
856
866
|
+ ` ${outstanding.map(one => one.id).join(', ')}. Submit the answer with`
|
|
857
867
|
+ ' submit_task_result, or call next_task with that taskId to read it again.');
|
|
858
868
|
}
|
|
859
|
-
return ok('No task right now.
|
|
860
|
-
+ '
|
|
869
|
+
return ok('No task right now. Read the matching project, story, conversion, or pipeline status;'
|
|
870
|
+
+ ' call next_task again only while it reports waiting for a model task.');
|
|
861
871
|
}
|
|
862
872
|
return ok(renderTaskEnvelope(task, { harness: deps.host.harness }), { taskId: task.id });
|
|
863
873
|
},
|
|
@@ -889,20 +899,18 @@ export const catalogue = [
|
|
|
889
899
|
const next = session.pendingTasks();
|
|
890
900
|
return ok(next > 0
|
|
891
901
|
? `Accepted. ${next} more task(s) waiting — call next_task.`
|
|
892
|
-
: 'Accepted.
|
|
902
|
+
: 'Accepted. Read the matching project, story, conversion, or pipeline status.');
|
|
893
903
|
},
|
|
894
904
|
},
|
|
895
905
|
{
|
|
896
906
|
name: 'next_question',
|
|
897
907
|
title: 'Get the question the platform is asking',
|
|
898
908
|
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.
|
|
900
|
-
+ '
|
|
909
|
+
+ ' Returns one question to put to them, or says there is none. Domain status responses also'
|
|
910
|
+
+ ' include parked questions directly.',
|
|
901
911
|
input: {
|
|
902
912
|
maxWaitSec: z.number().int().min(0).max(45).optional(),
|
|
903
913
|
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
914
|
projectId: z.string().optional(),
|
|
907
915
|
},
|
|
908
916
|
// Not `performsModelTasks`: a question has nothing to do with who performs the model calls,
|
|
@@ -912,12 +920,11 @@ export const catalogue = [
|
|
|
912
920
|
run: async (args, deps) => {
|
|
913
921
|
const session = await deps.session();
|
|
914
922
|
const project = projectOrNull(args, deps);
|
|
915
|
-
const jobId = typeof args.jobId === 'string' ? args.jobId : undefined;
|
|
916
923
|
// Asking for one by id re-reads it rather than taking a new one — the same reason
|
|
917
924
|
// `next_task` does: a parent that lost the envelope has no other way back to it.
|
|
918
925
|
if (typeof args.questionId === 'string') {
|
|
919
926
|
const known = session.questionById(args.questionId)
|
|
920
|
-
?? (project != null ? await parkedQuestion(deps, project
|
|
927
|
+
?? (project != null ? await parkedQuestion(deps, project) : null);
|
|
921
928
|
if (known == null || known.id !== args.questionId) {
|
|
922
929
|
return fail(`No question ${args.questionId} is waiting. Call next_question with no id.`);
|
|
923
930
|
}
|
|
@@ -935,12 +942,12 @@ export const catalogue = [
|
|
|
935
942
|
}
|
|
936
943
|
// Nothing queued here, which does not mean nothing is being asked: a run that parked while
|
|
937
944
|
// this connector was away is waiting on a question whose operation has long expired.
|
|
938
|
-
const parked = project != null ? await parkedQuestion(deps, project
|
|
945
|
+
const parked = project != null ? await parkedQuestion(deps, project) : null;
|
|
939
946
|
if (parked != null) {
|
|
940
947
|
return questionResult(parked, deps, 'This run parked on a question before this session attached. It is still open:');
|
|
941
948
|
}
|
|
942
|
-
return ok('No question right now.
|
|
943
|
-
+ '
|
|
949
|
+
return ok('No question right now. Read the matching domain status and answer any pendingInquiry it'
|
|
950
|
+
+ ' returns; call next_question again only while it reports waiting for a person.');
|
|
944
951
|
},
|
|
945
952
|
},
|
|
946
953
|
{
|
|
@@ -954,11 +961,6 @@ export const catalogue = [
|
|
|
954
961
|
answer: z.union([z.string(), z.array(z.string())]).optional(),
|
|
955
962
|
text: z.string().optional().describe('What they said, where the question takes words.'),
|
|
956
963
|
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
964
|
projectId: z.string().optional(),
|
|
963
965
|
},
|
|
964
966
|
availability: sessionCapable,
|
|
@@ -966,11 +968,10 @@ export const catalogue = [
|
|
|
966
968
|
const session = await deps.session();
|
|
967
969
|
const questionId = args.questionId;
|
|
968
970
|
const project = projectOrNull(args, deps);
|
|
969
|
-
const jobId = typeof args.jobId === 'string' ? args.jobId : undefined;
|
|
970
971
|
// The question this session is holding, if it still is. That is what decides HOW the answer
|
|
971
972
|
// travels: an operation the platform is waiting on, or the question's own id.
|
|
972
973
|
const held = session.questionById(questionId);
|
|
973
|
-
const inquiry = held ?? (project != null ? await parkedQuestion(deps, project
|
|
974
|
+
const inquiry = held ?? (project != null ? await parkedQuestion(deps, project) : null);
|
|
974
975
|
if (inquiry == null || inquiry.id !== questionId) {
|
|
975
976
|
return fail(`No question ${questionId} is waiting. Call next_question for the current one.`);
|
|
976
977
|
}
|
|
@@ -989,7 +990,7 @@ export const catalogue = [
|
|
|
989
990
|
}
|
|
990
991
|
await deps.api.inquiry.answer(project, questionId, answer);
|
|
991
992
|
}
|
|
992
|
-
return ok('Recorded.
|
|
993
|
+
return ok('Recorded. Read the matching project, story, conversion, or pipeline status.');
|
|
993
994
|
},
|
|
994
995
|
},
|
|
995
996
|
{
|
|
@@ -1018,7 +1019,7 @@ export const catalogue = [
|
|
|
1018
1019
|
title: 'Convert an application you already have',
|
|
1019
1020
|
description: 'Bring an existing application onto the platform: it reads the code, restores the'
|
|
1020
1021
|
+ ' specification and the user stories nobody wrote down, and rebuilds it on the platform\'s'
|
|
1021
|
-
+ ' stack. The original is kept beside it. Returns
|
|
1022
|
+
+ ' stack. The original is kept beside it. Returns conversion status and stops at your decision after'
|
|
1022
1023
|
+ ' each stage.',
|
|
1023
1024
|
input: {
|
|
1024
1025
|
projectId: z.string().optional().describe('Convert into a project that already exists.'),
|
|
@@ -1033,7 +1034,7 @@ export const catalogue = [
|
|
|
1033
1034
|
const named = typeof args.projectId === 'string' ? args.projectId : deps.attached();
|
|
1034
1035
|
if (named != null) {
|
|
1035
1036
|
await ensureSession(deps, named);
|
|
1036
|
-
return
|
|
1037
|
+
return conversionResult(await deps.api.convert.start(named));
|
|
1037
1038
|
}
|
|
1038
1039
|
const repoUrl = typeof args.repoUrl === 'string' && args.repoUrl.trim() !== ''
|
|
1039
1040
|
? args.repoUrl.trim()
|
|
@@ -1042,7 +1043,7 @@ export const catalogue = [
|
|
|
1042
1043
|
return fail('Nothing to convert. Name a project, attach one, run the connector in the directory you'
|
|
1043
1044
|
+ ' want converted, or give a repoUrl.');
|
|
1044
1045
|
}
|
|
1045
|
-
const
|
|
1046
|
+
const status = await deps.api.convert.create({
|
|
1046
1047
|
...(typeof args.name === 'string' ? { name: args.name } : {}),
|
|
1047
1048
|
...(typeof args.about === 'string' ? { about: args.about } : {}),
|
|
1048
1049
|
// A NAMED repository outranks the directory this connector runs in. A stdio connector
|
|
@@ -1062,9 +1063,9 @@ export const catalogue = [
|
|
|
1062
1063
|
},
|
|
1063
1064
|
}),
|
|
1064
1065
|
});
|
|
1065
|
-
deps.attach(
|
|
1066
|
-
await ensureSession(deps,
|
|
1067
|
-
return
|
|
1066
|
+
deps.attach(status.projectId);
|
|
1067
|
+
await ensureSession(deps, status.projectId);
|
|
1068
|
+
return conversionResult(await deps.api.convert.start(status.projectId));
|
|
1068
1069
|
}),
|
|
1069
1070
|
},
|
|
1070
1071
|
{
|
|
@@ -1072,7 +1073,7 @@ export const catalogue = [
|
|
|
1072
1073
|
title: 'Decide what a conversion does next',
|
|
1073
1074
|
description: 'A conversion stops after each stage and waits for a decision: analyze what was read,'
|
|
1074
1075
|
+ ' extract the user stories, implement them, leave it as it stands, retry a stage that'
|
|
1075
|
-
+ ' failed, or cancel. Returns
|
|
1076
|
+
+ ' failed, or cancel. Returns the conversion status.',
|
|
1076
1077
|
input: {
|
|
1077
1078
|
decision: z.enum(Object.values(ConversionDecision)),
|
|
1078
1079
|
projectId: z.string().optional(),
|
|
@@ -1082,8 +1083,8 @@ export const catalogue = [
|
|
|
1082
1083
|
run: async (args, deps) => await answering(deps, 'proceed_conversion', async () => {
|
|
1083
1084
|
const project = projectOf(args, deps);
|
|
1084
1085
|
await ensureSession(deps, project);
|
|
1085
|
-
const
|
|
1086
|
-
return
|
|
1086
|
+
const status = await deps.api.convert.proceed(project, args.decision, typeof args.note === 'string' ? args.note : undefined);
|
|
1087
|
+
return conversionResult(status);
|
|
1087
1088
|
}),
|
|
1088
1089
|
},
|
|
1089
1090
|
{
|
|
@@ -1117,7 +1118,7 @@ export const catalogue = [
|
|
|
1117
1118
|
}
|
|
1118
1119
|
const project = projectOf(args, deps);
|
|
1119
1120
|
await ensureSession(deps, project);
|
|
1120
|
-
return
|
|
1121
|
+
return conversionResult(await deps.api.convert.purge(project));
|
|
1121
1122
|
}),
|
|
1122
1123
|
},
|
|
1123
1124
|
{
|