@ctrl-spc/cs 0.7.8 → 0.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp.js +40 -23
- package/dist/panel3/prompt.js +6 -0
- package/dist/panel3/tools.js +16 -7
- package/dist/workflow-tool-mentions.js +37 -2
- package/dist/workflows.js +4 -4
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -14,8 +14,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
14
14
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
15
15
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
16
16
|
import { z } from 'zod';
|
|
17
|
-
import { workflowToolPermission, validateWorkflowToolTarget, WORKFLOW_TOOL_TEACHING,
|
|
18
|
-
import { workflowToolForName } from './workflow-tool-mentions.js';
|
|
17
|
+
import { workflowToolPermission, validateWorkflowToolTarget, WORKFLOW_TOOL_TEACHING, WORKFLOW_AUTHORING_TEACHING, authorToolMentions } from './workflows.js';
|
|
18
|
+
import { workflowToolForName, validateApprovalAction } from './workflow-tool-mentions.js';
|
|
19
19
|
import { TOOLS_SERVER_PORT, SESSION_TTL_MS } from './env.js';
|
|
20
20
|
import { agentPath } from './agents.js';
|
|
21
21
|
import { mcpToken, readMcpToken, readSession } from './config.js';
|
|
@@ -7802,24 +7802,19 @@ export async function readWorkflowLibraryHandler(client, args, userId) {
|
|
|
7802
7802
|
// workflow ids actually present rather than as PostgREST embeds, the same
|
|
7803
7803
|
// two-step `listClientsHandler` uses.
|
|
7804
7804
|
const workflowIds = workflows.map((workflow) => workflow.id);
|
|
7805
|
-
const links =
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
.select('workflow_id, stage_id, to_stage_id, condition, position')
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
? ((await must(client
|
|
7819
|
-
.from('cliv2_workflow_branches')
|
|
7820
|
-
.select('workflow_id, when_condition, runs_workflow_id, position')
|
|
7821
|
-
.in('workflow_id', workflowIds))) ?? [])
|
|
7822
|
-
: [];
|
|
7805
|
+
const links = [];
|
|
7806
|
+
const exits = [];
|
|
7807
|
+
const branches = [];
|
|
7808
|
+
// Like artifact counts above, bound GET filters so a large library remains readable.
|
|
7809
|
+
for (let offset = 0; offset < workflowIds.length; offset += COUNT_BATCH) {
|
|
7810
|
+
const ids = workflowIds.slice(offset, offset + COUNT_BATCH);
|
|
7811
|
+
links.push(...(await must(client.from('cliv2_workflow_stages_in_workflow')
|
|
7812
|
+
.select('workflow_id, stage_id, position').in('workflow_id', ids))) ?? []);
|
|
7813
|
+
exits.push(...(await must(client.from('cliv2_workflow_stage_exits')
|
|
7814
|
+
.select('workflow_id, stage_id, to_stage_id, condition, position').in('workflow_id', ids))) ?? []);
|
|
7815
|
+
branches.push(...(await must(client.from('cliv2_workflow_branches')
|
|
7816
|
+
.select('workflow_id, when_condition, runs_workflow_id, position').in('workflow_id', ids))) ?? []);
|
|
7817
|
+
}
|
|
7823
7818
|
const orgIds = [
|
|
7824
7819
|
...new Set([...stages.map((s) => s.org_id), ...workflows.map((w) => w.org_id)]),
|
|
7825
7820
|
];
|
|
@@ -7952,7 +7947,7 @@ export async function buildWorkflowHandler(client, args, fromAgent) {
|
|
|
7952
7947
|
prose.push([`stages[${index}].description`, stage.description]);
|
|
7953
7948
|
}
|
|
7954
7949
|
if (typeof stage.body === 'string') {
|
|
7955
|
-
|
|
7950
|
+
stage.body = authorToolMentions(stage.body);
|
|
7956
7951
|
prose.push([`stages[${index}].body`, stage.body]);
|
|
7957
7952
|
}
|
|
7958
7953
|
});
|
|
@@ -8050,7 +8045,7 @@ export async function editWorkflowHandler(client, args, fromAgent) {
|
|
|
8050
8045
|
prose.push([`stages[${index}].description`, stage.description]);
|
|
8051
8046
|
}
|
|
8052
8047
|
if (typeof stage.body === 'string') {
|
|
8053
|
-
|
|
8048
|
+
stage.body = authorToolMentions(stage.body);
|
|
8054
8049
|
prose.push([`stages[${index}].body`, stage.body]);
|
|
8055
8050
|
}
|
|
8056
8051
|
});
|
|
@@ -8693,6 +8688,7 @@ export const TOOL_NAMES = [
|
|
|
8693
8688
|
// 18c Slice 1: the run's own activity, unasked, on the request's card.
|
|
8694
8689
|
'report_activity',
|
|
8695
8690
|
'ask_question',
|
|
8691
|
+
'require_approval',
|
|
8696
8692
|
'present_wireframes',
|
|
8697
8693
|
'present_mocks',
|
|
8698
8694
|
'resolve_feedback',
|
|
@@ -8801,6 +8797,7 @@ const SERVER_INSTRUCTIONS = `You are connected to CTRL+SPC, where this user's wo
|
|
|
8801
8797
|
- The PLAN for a work item: create_stage and create_step to lay it out, then update_step to record progress — in_progress when you start, a rewritten next_action as it changes, done when finished. Never create a second step to report on the first. These need a WORK ITEM and an approved scope: they are its committed plan, not a log of what you did. A panel request with no work item has no plan to write, so use report_activity and, if the work deserves an item of its own, ask for one. Never attach it to an unrelated work item to get past a refusal.
|
|
8802
8798
|
- Anything learned, decided or discovered: record_user_input, add_comment, record_context_exploration, propose_project_context. The transcript is never the record.
|
|
8803
8799
|
- Following a process: read_workflow_library, then start_workflow on the work item so its stages appear where the user can watch them.
|
|
8800
|
+
- Creating a reusable workflow: build_workflow. Describe the requested process in its stages; do not execute those stages or create a plan artifact about creating the workflow. Ask only the clarification needed to author it, through ask_question. A work item used to hold this conversation does not turn workflow authoring into code implementation. ${WORKFLOW_AUTHORING_TEACHING}
|
|
8804
8801
|
- A method the team has already written down: list_skills, then get_skill. A skill is a document TO FOLLOW for the rest of the work, not reference material to summarise. Look before improvising your own method.
|
|
8805
8802
|
- A secret the work needs: list_credentials, then get_credential. Fetch it rather than asking the user for something they have already stored, and never write a value into a file, a commit, a log or your answer.
|
|
8806
8803
|
Call list_tasks or get_task first when you need to know what the work is, and LOOK BEFORE YOU CREATE: if a work item already covers this, use it instead of adding a near-duplicate. If you are unsure whether a tool exists, look before falling back to the terminal.
|
|
@@ -10726,8 +10723,28 @@ runTodoIdSource = null) {
|
|
|
10726
10723
|
touchSession(connectionId);
|
|
10727
10724
|
return readWorkflowLibraryHandler(client, args, userId);
|
|
10728
10725
|
});
|
|
10726
|
+
server.registerTool('require_approval', {
|
|
10727
|
+
description: 'Pause for the user to approve a specific action before you perform it, including editing code, running a migration, or pushing commits. Name its exact scope in action and details; use relative paths, never absolute local paths. Stop on pending or denial; retry the identical call after the answer. Only an approved result permits that action. This tool does not execute it. ' + WORKFLOW_TOOL_TEACHING,
|
|
10728
|
+
inputSchema: {
|
|
10729
|
+
action: z.string().trim().min(1).max(240),
|
|
10730
|
+
details: z.string().trim().min(1).max(8000),
|
|
10731
|
+
workflow_tool_instance: z.string().uuid().optional(),
|
|
10732
|
+
},
|
|
10733
|
+
}, async (args) => {
|
|
10734
|
+
touchSession(connectionId);
|
|
10735
|
+
try {
|
|
10736
|
+
validateApprovalAction(args.action, args.details);
|
|
10737
|
+
const session = openSessions.get(connectionId) ?? null;
|
|
10738
|
+
await permissionForWorkflowCall(client, session, 'require_approval', args);
|
|
10739
|
+
return runGated(client, userId, session, 'require_approval', args, `${args.action} — ${args.details}`, async () => textResult({ approved: true, action: args.action, details: args.details,
|
|
10740
|
+
instruction: 'The user approved only this action and scope. You may now perform it.' }), await runTodoIdOf());
|
|
10741
|
+
}
|
|
10742
|
+
catch (error) {
|
|
10743
|
+
return errorResult(errorMessage(error));
|
|
10744
|
+
}
|
|
10745
|
+
});
|
|
10729
10746
|
server.registerTool('build_workflow', {
|
|
10730
|
-
description: 'Build a WHOLE workflow in one call in CTRL+SPC — its stages, the order they run in, the ' +
|
|
10747
|
+
description: WORKFLOW_AUTHORING_TEACHING + 'Build a WHOLE workflow in one call in CTRL+SPC — its stages, the order they run in, the ' +
|
|
10731
10748
|
'loops back between them, and the branch conditions that apply everywhere. This is the ' +
|
|
10732
10749
|
'only way to create a workflow: there is no create-stage tool, because assembling one ' +
|
|
10733
10750
|
'piece at a time is the work this removes. ' +
|
package/dist/panel3/prompt.js
CHANGED
|
@@ -598,6 +598,12 @@ export function workBrief(level, responsibility, boundary, workItemId, attachmen
|
|
|
598
598
|
'time, and before any reading that takes a while. It does not end your turn and it is not',
|
|
599
599
|
'your reply; you carry on straight afterwards.',
|
|
600
600
|
'',
|
|
601
|
+
'WORKFLOW AUTHORING',
|
|
602
|
+
'When asked to create a workflow, use create_workflow to save it; do not execute the workflow.',
|
|
603
|
+
'Ask clarifying questions with ask_question only when the missing answer changes its behavior.',
|
|
604
|
+
'Write the requested tool mentions and per-use permissions in the stage bodies using the tool schema instructions.',
|
|
605
|
+
'The saved workflow appears as a reviewable card in this conversation. Do not substitute a plan artifact or a prose-only reply.',
|
|
606
|
+
'',
|
|
601
607
|
'TRACEABILITY BEFORE A CODEBASE FILE CHANGES',
|
|
602
608
|
'A codebase-file change means ANY file change: source, config, docs, tests, migrations,',
|
|
603
609
|
'generated inputs, and assets all count. "Non-code" means no codebase file changes at all.',
|
package/dist/panel3/tools.js
CHANGED
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
// `import type` loses its v3 header in the published `dist/`.
|
|
115
115
|
import { presentDesign } from '../design-review.js';
|
|
116
116
|
import { canonicalArgsHash, getDocumentHandler, proposeProjectContextHandler, placeWorkItemHandler, resolveFeedbackHandler, PROJECT_DOCUMENT_TYPES, errorMessage } from '../product-tools.js';
|
|
117
|
-
import { workflowToolForName, workflowToolAlwaysAllowed } from '../workflow-tool-mentions.js';
|
|
117
|
+
import { workflowToolForName, workflowToolAlwaysAllowed, validateApprovalAction } from '../workflow-tool-mentions.js';
|
|
118
118
|
import { captureUrlScreenshot } from '../browser.js';
|
|
119
119
|
import { createServer as createHttpServer } from 'node:http';
|
|
120
120
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
@@ -130,7 +130,7 @@ import { workBrief } from './prompt.js';
|
|
|
130
130
|
here is the same fact without a second round trip. */
|
|
131
131
|
import { harness } from './spawn.js';
|
|
132
132
|
import { listCodebases } from '../codebases.js';
|
|
133
|
-
import { buildWorkflow, duplicateWorkflow, editWorkflow, readWorkflow, rewordStage, workflowToolPermission, validateWorkflowToolTarget, WORKFLOW_TOOL_TEACHING } from '../workflows.js';
|
|
133
|
+
import { buildWorkflow, duplicateWorkflow, editWorkflow, readWorkflow, rewordStage, workflowToolPermission, validateWorkflowToolTarget, WORKFLOW_TOOL_TEACHING, WORKFLOW_AUTHORING_TEACHING } from '../workflows.js';
|
|
134
134
|
/* ═══ 38-panel3-steps: THE FIFTH NEUTRAL MODULE, AND A DECISION LIKE THE OTHERS.
|
|
135
135
|
═══ Stages and steps are the work item's own record, shared by both
|
|
136
136
|
generations and read by the web's Steps section. `steps.ts` owns the rows so
|
|
@@ -749,6 +749,13 @@ function productResult(result) {
|
|
|
749
749
|
return text;
|
|
750
750
|
}
|
|
751
751
|
const TOOLS = [
|
|
752
|
+
{
|
|
753
|
+
name: 'require_approval', levels: [1, 2],
|
|
754
|
+
description: 'Pause for the user to approve a specific action before you perform it, including editing code, running a migration, or pushing commits. Name the action and exact scope using relative paths, never absolute local paths. Stop on pending or denial and retry the identical call after the answer. Only an approved result permits that action. This tool does not execute it.',
|
|
755
|
+
input: { action: z.string().trim().min(1).max(240), details: z.string().trim().min(1).max(8000) },
|
|
756
|
+
handler: async (_caller, args) => JSON.stringify({ approved: true, action: args.action, details: args.details,
|
|
757
|
+
instruction: 'The user approved only this action and scope. You may now perform it.' }),
|
|
758
|
+
},
|
|
752
759
|
{
|
|
753
760
|
name: 'get_document', levels: ALL,
|
|
754
761
|
description: 'Read a project context document or instruction in full by id. Read-only.',
|
|
@@ -1164,7 +1171,7 @@ const TOOLS = [
|
|
|
1164
1171
|
described and can write it down as it was described. A launcher writes one
|
|
1165
1172
|
line and exits; a worker owns one piece of somebody else's process. */
|
|
1166
1173
|
levels: [2],
|
|
1167
|
-
description: "Write a new workflow into this organisation's library: a name, what it is about, and its "
|
|
1174
|
+
description: WORKFLOW_AUTHORING_TEACHING + "Write a new workflow into this organisation's library: a name, what it is about, and its "
|
|
1168
1175
|
+ 'stages in order, each with a one-line description and the markdown document an agent '
|
|
1169
1176
|
+ 'following it reads. Stages are numbered from 1, the way get_workflow prints them. exits '
|
|
1170
1177
|
+ 'are the conditional ways back: from_stage N, if condition, go back to to_stage M, where M '
|
|
@@ -3115,6 +3122,8 @@ async function invokeWorkflowTool(caller, tool, args) {
|
|
|
3115
3122
|
const invoke = () => tool.handler(caller, inputs);
|
|
3116
3123
|
if (!canonical || workflowToolAlwaysAllowed(canonical))
|
|
3117
3124
|
return invoke();
|
|
3125
|
+
if (canonical === 'require_approval')
|
|
3126
|
+
validateApprovalAction(String(a.action), String(a.details));
|
|
3118
3127
|
const attached = await loadAttachments(caller.client, caller.cardId);
|
|
3119
3128
|
const ids = attached.filter(item => item.kind === 'work_item').map(item => item.ref_id);
|
|
3120
3129
|
let workItemId = explicitSource;
|
|
@@ -3135,9 +3144,9 @@ async function invokeWorkflowTool(caller, tool, args) {
|
|
|
3135
3144
|
throw new Error('This planning action requires an explicit tool mention in the current workflow stage. Nothing was written.');
|
|
3136
3145
|
if (permission)
|
|
3137
3146
|
await validateWorkflowToolTarget(caller.client, workItemId, canonical, a);
|
|
3138
|
-
if (!permission || permission.approval === 'not-required')
|
|
3147
|
+
if (canonical !== 'require_approval' && (!permission || permission.approval === 'not-required'))
|
|
3139
3148
|
return invoke();
|
|
3140
|
-
const fingerprint = canonicalArgsHash({ tool: tool.name, workItemId, instance: permission
|
|
3149
|
+
const fingerprint = canonicalArgsHash({ tool: tool.name, workItemId, instance: permission?.id ?? null, args: inputs });
|
|
3141
3150
|
const category = `workflow_tool_${fingerprint}`;
|
|
3142
3151
|
const questions = await withAskContent(caller.client, await rows(caller.client.from('panel3_asks').select(`pending_run_id, answered_at, ${ASK_CONTENT_COLUMNS}`).eq('card_id', caller.cardId), 'read', 'workflow tool approvals'));
|
|
3143
3152
|
const previous = questions.find(question => question.category === category && question.decision_id !== null);
|
|
@@ -3151,8 +3160,8 @@ async function invokeWorkflowTool(caller, tool, args) {
|
|
|
3151
3160
|
if (caller.level === 3)
|
|
3152
3161
|
throw new Error('This workflow tool action requires user approval. Escalate the exact action to the conversation owner and stop; the owner must perform the approved call.');
|
|
3153
3162
|
await asked(caller, {
|
|
3154
|
-
question: `May I ${tool.name.replaceAll('_', ' ')}${a.title ? `: ${String(a.title).slice(0, 180)}` : ''}?`,
|
|
3155
|
-
category, context: 'This use of the tool requires approval in the workflow stage.',
|
|
3163
|
+
question: canonical === 'require_approval' ? `May I ${String(a.action)}?` : `May I ${tool.name.replaceAll('_', ' ')}${a.title ? `: ${String(a.title).slice(0, 180)}` : ''}?`,
|
|
3164
|
+
category, context: canonical === 'require_approval' ? String(a.details) : 'This use of the tool requires approval in the workflow stage.',
|
|
3156
3165
|
answer_mode: 'single_select', options: ['approve', 'deny'], work_item_id: workItemId,
|
|
3157
3166
|
}, true);
|
|
3158
3167
|
throw new Error('This workflow tool action is waiting for user approval. Nothing was written. Stop until the user answers, then retry the same call with the same arguments.');
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { absolutePathToken } from './local-paths.js';
|
|
1
2
|
/** Shared by the stage editor and both tool servers. The stage body remains
|
|
2
3
|
* the record: each Markdown link names one invocation, never a tool-wide grant. */
|
|
3
4
|
export const WORKFLOW_TOOLS = [
|
|
5
|
+
{ id: 'require_approval', name: 'Require Approval', detail: 'Pause before any action, such as editing code, running migrations, or pushing commits.', alwaysAllowed: false, advanced: false },
|
|
4
6
|
{ id: 'create_artifact', name: 'Create Artifact', detail: 'Write a user story, plan, spec, diagram, or interactive mock.', alwaysAllowed: false, advanced: false },
|
|
5
7
|
{ id: 'update_artifact', name: 'Edit Artifact', detail: 'Revise an existing artifact.', alwaysAllowed: false, advanced: false },
|
|
6
8
|
{ id: 'ask_question', name: 'Ask Question', detail: 'Ask for feedback, a decision, or approval. Always available.', alwaysAllowed: true, advanced: false },
|
|
@@ -35,7 +37,7 @@ export function workflowToolForName(name) {
|
|
|
35
37
|
}
|
|
36
38
|
export function toolMentionText(tool, id, approval = 'required') {
|
|
37
39
|
const entry = WORKFLOW_TOOLS.find(entry => entry.id === tool);
|
|
38
|
-
return `[@${entry.name}](ctrl-spc://tool/${tool}/${id}?approval=${workflowToolAlwaysAllowed(tool) ? 'not-required' : approval})`;
|
|
40
|
+
return `[@${entry.name}](ctrl-spc://tool/${tool}/${id}?approval=${tool === 'require_approval' ? 'required' : workflowToolAlwaysAllowed(tool) ? 'not-required' : approval})`;
|
|
39
41
|
}
|
|
40
42
|
export function toolMentions(body) {
|
|
41
43
|
const mentions = [];
|
|
@@ -45,7 +47,7 @@ export function toolMentions(body) {
|
|
|
45
47
|
continue;
|
|
46
48
|
mentions.push({
|
|
47
49
|
id: match[2], tool: match[1],
|
|
48
|
-
approval: workflowToolAlwaysAllowed(match[1]) ? 'not-required' : match[3],
|
|
50
|
+
approval: match[1] === 'require_approval' ? 'required' : workflowToolAlwaysAllowed(match[1]) ? 'not-required' : match[3],
|
|
49
51
|
from: match.index, to: match.index + match[0].length,
|
|
50
52
|
});
|
|
51
53
|
}
|
|
@@ -78,6 +80,7 @@ export const WORKFLOW_TOOL_TEACHING = 'Tool mentions in workflow stage documents
|
|
|
78
80
|
'the tool itself opens the permission question before writing anything. Do not open a separate ask_question for permission to use that tool. ' +
|
|
79
81
|
'When the user approves that tool question, retry the same call with exactly the same arguments. Read tools and Ask Question never need permission. Permission does not grant access to another project or change your agent level. If an action is only available to your coordinator, escalate that action with its mention id. ' +
|
|
80
82
|
'Approval to use a tool is separate from approval of its output: still ask every review question written in the stage. Present Mock and Present Wireframe open their own output review; do not ask a duplicate review question. Proposing a scope or context change never accepts it. ' +
|
|
83
|
+
'Require Approval is a separate checkpoint for ANY action, including native shell and code tools: call require_approval with the specific action and its scope BEFORE doing it, then stop until that tool returns approval. It does not execute the action or approve other actions. Do not use ask_question instead of this checkpoint. ' +
|
|
81
84
|
'If your level cannot ask the user, escalate the review to the conversation owner and stop.';
|
|
82
85
|
/** Agents may preserve existing settings, but cannot author their own grants. */
|
|
83
86
|
export function assertAgentToolMentions(body, previous = '') {
|
|
@@ -91,3 +94,35 @@ export function assertAgentToolMentions(body, previous = '') {
|
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
}
|
|
97
|
+
/** Library authoring accepts compact links; the server owns occurrence IDs so
|
|
98
|
+
* agent-created mentions render exactly like ones inserted in the editor. */
|
|
99
|
+
export function authorToolMentions(body) {
|
|
100
|
+
const expanded = body.replace(/\[@[^\]\n]+\]\(ctrl-spc:\/\/tool\/([a-z_]+)\?approval=(required|not-required)\)/g, (_match, name, approval) => {
|
|
101
|
+
const tool = workflowToolForName(name);
|
|
102
|
+
if (!tool)
|
|
103
|
+
throw new Error(`Unknown workflow tool: ${name}. Nothing was written.`);
|
|
104
|
+
return toolMentionText(tool, crypto.randomUUID(), approval);
|
|
105
|
+
});
|
|
106
|
+
validateToolMentions(expanded);
|
|
107
|
+
let result = expanded;
|
|
108
|
+
for (const mention of toolMentions(expanded).reverse()) {
|
|
109
|
+
result = result.slice(0, mention.from) + toolMentionText(mention.tool, crypto.randomUUID(), mention.approval) + result.slice(mention.to);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
export const WORKFLOW_AUTHORING_TEACHING = 'When the user asks you to create a workflow, build it in the library; do not merely describe it or execute it. ' +
|
|
114
|
+
'Use ask_question for missing requirements that materially change the workflow, then continue after the answer. ' +
|
|
115
|
+
'Encode every requested tool use and permission as a Markdown mention in the stage body. ' +
|
|
116
|
+
'Use compact links: [@Create Artifact](ctrl-spc://tool/create_artifact?approval=not-required) to write a plan without asking; ' +
|
|
117
|
+
'[@Ask Question](ctrl-spc://tool/ask_question?approval=not-required) to review its output; ' +
|
|
118
|
+
'before editing code, [@Require Approval](ctrl-spc://tool/require_approval?approval=required). ' +
|
|
119
|
+
'The server assigns unique IDs and the user sees editable controls in the stage editor. ' +
|
|
120
|
+
'Set not-required only when the user permits that use. Otherwise default to required. Never convert permission to write a plan into permission to execute it. ' +
|
|
121
|
+
'Each occurrence has its own setting. Put the action and scope beside each Require Approval mention, including migrations or pushing commits. ' +
|
|
122
|
+
'Available tool IDs: ' + WORKFLOW_TOOLS.map(tool => `${tool.id} (${tool.name})`).join(', ') + '. ';
|
|
123
|
+
/** Approval prose is displayed in the hosted app; keep machine-local paths local. */
|
|
124
|
+
export function validateApprovalAction(action, details) {
|
|
125
|
+
if (absolutePathToken(action) || absolutePathToken(details)) {
|
|
126
|
+
throw new Error('Use repo-relative or scratch-relative paths in the approval action and scope, never absolute local paths. No approval was requested.');
|
|
127
|
+
}
|
|
128
|
+
}
|
package/dist/workflows.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { absolutePathToken } from './local-paths.js';
|
|
2
2
|
import { listSteps } from './steps.js';
|
|
3
|
-
import { assertAgentToolMentions, toolMentions, validateToolMentions } from './workflow-tool-mentions.js';
|
|
4
|
-
export { WORKFLOW_TOOL_TEACHING, assertAgentToolMentions } from './workflow-tool-mentions.js';
|
|
3
|
+
import { assertAgentToolMentions, authorToolMentions, toolMentions, validateToolMentions } from './workflow-tool-mentions.js';
|
|
4
|
+
export { WORKFLOW_TOOL_TEACHING, WORKFLOW_AUTHORING_TEACHING, authorToolMentions, assertAgentToolMentions } from './workflow-tool-mentions.js';
|
|
5
5
|
/** Resolve permission from the current stage's saved document, never agent
|
|
6
6
|
* prose or a supplied boolean. Both harnesses use the same stage resolver. */
|
|
7
7
|
export async function workflowToolPermission(client, workItemId, tool, instance) {
|
|
@@ -140,7 +140,7 @@ export async function readWorkflow(client, workflowId) {
|
|
|
140
140
|
export async function buildWorkflow(client, input) {
|
|
141
141
|
const prose = [['name', input.name], ['description', input.description]];
|
|
142
142
|
input.stages.forEach((stage, i) => {
|
|
143
|
-
|
|
143
|
+
stage.body = authorToolMentions(stage.body);
|
|
144
144
|
prose.push([`stage ${i + 1} name`, stage.name], [`stage ${i + 1} description`, stage.description], [`stage ${i + 1} body`, stage.body]);
|
|
145
145
|
});
|
|
146
146
|
input.exits.forEach((exit, i) => prose.push([`exit ${i + 1} condition`, exit.condition]));
|
|
@@ -253,7 +253,7 @@ export async function editWorkflow(client, input) {
|
|
|
253
253
|
input.stages.forEach((stage, i) => {
|
|
254
254
|
if ('stage_id' in stage)
|
|
255
255
|
return;
|
|
256
|
-
|
|
256
|
+
stage.body = authorToolMentions(stage.body);
|
|
257
257
|
prose.push([`stage ${i + 1} name`, stage.name], [`stage ${i + 1} description`, stage.description], [`stage ${i + 1} body`, stage.body]);
|
|
258
258
|
});
|
|
259
259
|
input.exits.forEach((exit, i) => prose.push([`exit ${i + 1} condition`, exit.condition]));
|
package/package.json
CHANGED