@cat-factory/orchestration 0.304.0 → 0.306.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/container/assistant-module.d.ts +48 -0
- package/dist/container/assistant-module.d.ts.map +1 -0
- package/dist/container/assistant-module.js +87 -0
- package/dist/container/assistant-module.js.map +1 -0
- package/dist/container/engine-collaborators.d.ts +3 -0
- package/dist/container/engine-collaborators.d.ts.map +1 -1
- package/dist/container/engine-collaborators.js +6 -1
- package/dist/container/engine-collaborators.js.map +1 -1
- package/dist/container.d.ts +8 -0
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +1 -0
- package/dist/container.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/assistant/AssistantService.d.ts +85 -0
- package/dist/modules/assistant/AssistantService.d.ts.map +1 -0
- package/dist/modules/assistant/AssistantService.js +254 -0
- package/dist/modules/assistant/AssistantService.js.map +1 -0
- package/dist/modules/assistant/actions/deps.d.ts +71 -0
- package/dist/modules/assistant/actions/deps.d.ts.map +1 -0
- package/dist/modules/assistant/actions/deps.js +2 -0
- package/dist/modules/assistant/actions/deps.js.map +1 -0
- package/dist/modules/assistant/actions/index.d.ts +20 -0
- package/dist/modules/assistant/actions/index.d.ts.map +1 -0
- package/dist/modules/assistant/actions/index.js +28 -0
- package/dist/modules/assistant/actions/index.js.map +1 -0
- package/dist/modules/assistant/actions/serviceDependency.d.ts +4 -0
- package/dist/modules/assistant/actions/serviceDependency.d.ts.map +1 -0
- package/dist/modules/assistant/actions/serviceDependency.js +99 -0
- package/dist/modules/assistant/actions/serviceDependency.js.map +1 -0
- package/dist/modules/assistant/actions/serviceFromRepo.d.ts +4 -0
- package/dist/modules/assistant/actions/serviceFromRepo.d.ts.map +1 -0
- package/dist/modules/assistant/actions/serviceFromRepo.js +101 -0
- package/dist/modules/assistant/actions/serviceFromRepo.js.map +1 -0
- package/dist/modules/assistant/actions/shared.d.ts +31 -0
- package/dist/modules/assistant/actions/shared.d.ts.map +1 -0
- package/dist/modules/assistant/actions/shared.js +37 -0
- package/dist/modules/assistant/actions/shared.js.map +1 -0
- package/dist/modules/assistant/actions/taskFromIssue.d.ts +4 -0
- package/dist/modules/assistant/actions/taskFromIssue.d.ts.map +1 -0
- package/dist/modules/assistant/actions/taskFromIssue.js +144 -0
- package/dist/modules/assistant/actions/taskFromIssue.js.map +1 -0
- package/dist/modules/assistant/assistant.logic.d.ts +126 -0
- package/dist/modules/assistant/assistant.logic.d.ts.map +1 -0
- package/dist/modules/assistant/assistant.logic.js +215 -0
- package/dist/modules/assistant/assistant.logic.js.map +1 -0
- package/dist/modules/assistant/types.d.ts +45 -0
- package/dist/modules/assistant/types.d.ts.map +1 -0
- package/dist/modules/assistant/types.js +9 -0
- package/dist/modules/assistant/types.js.map +1 -0
- package/dist/modules/board/BoardService.d.ts +9 -3
- package/dist/modules/board/BoardService.d.ts.map +1 -1
- package/dist/modules/board/BoardService.js +8 -2
- package/dist/modules/board/BoardService.js.map +1 -1
- package/dist/modules/board/serviceRepoLinkage.d.ts +16 -1
- package/dist/modules/board/serviceRepoLinkage.d.ts.map +1 -1
- package/dist/modules/board/serviceRepoLinkage.js.map +1 -1
- package/dist/modules/execution/HumanTestController.d.ts.map +1 -1
- package/dist/modules/execution/HumanTestController.js +3 -6
- package/dist/modules/execution/HumanTestController.js.map +1 -1
- package/dist/modules/execution/RunStateMachine.d.ts +13 -4
- package/dist/modules/execution/RunStateMachine.d.ts.map +1 -1
- package/dist/modules/execution/RunStateMachine.js +18 -14
- package/dist/modules/execution/RunStateMachine.js.map +1 -1
- package/dist/modules/execution/VisualConfirmationController.d.ts.map +1 -1
- package/dist/modules/execution/VisualConfirmationController.js +3 -6
- package/dist/modules/execution/VisualConfirmationController.js.map +1 -1
- package/dist/modules/notifications/NotificationService.d.ts +46 -7
- package/dist/modules/notifications/NotificationService.d.ts.map +1 -1
- package/dist/modules/notifications/NotificationService.js +60 -18
- package/dist/modules/notifications/NotificationService.js.map +1 -1
- package/package.json +11 -11
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { generateText } from 'ai';
|
|
2
|
+
import { ASSISTANT_AGENT_KIND, ASSISTANT_SYSTEM_PROMPT, catFactoryObservability, renderAssistantPrompt, } from '@cat-factory/agents';
|
|
3
|
+
import { validateDescriptorFields } from '@cat-factory/contracts';
|
|
4
|
+
import { describeError, extractJson, getErrorMessage, RateLimitedError, resolveScopedModelProvider, UnavailableError, } from '@cat-factory/kernel';
|
|
5
|
+
import { resolveInlineBlockModelRef } from '../../inlineBlockModel.js';
|
|
6
|
+
import { assistantActionBriefs, keepDeclaredArguments, readAssistantSelection, } from './assistant.logic.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// The IN-APP ASSISTANT: one prompt, one inline model call that ROUTES it, one action performed by
|
|
9
|
+
// the platform.
|
|
10
|
+
//
|
|
11
|
+
// Structurally the `BugHuntAssessorService` twin (an un-run-scoped inline call built from the
|
|
12
|
+
// model dependencies every facade already wires, so the feature needs no per-facade wiring of its
|
|
13
|
+
// own), and it inherits that service's two standing obligations for such a call: it answers to the
|
|
14
|
+
// workspace BUDGET that no run start gates it behind, and it degrades a container-only
|
|
15
|
+
// subscription ref to something an inline `generateText` can serve.
|
|
16
|
+
//
|
|
17
|
+
// What is different, and is the whole point of the surface, is that the reply is not an answer to
|
|
18
|
+
// show anyone. It is a ROUTING DECISION the platform then acts on, so everything between the model
|
|
19
|
+
// and the side effect is deterministic: the reply is parsed, the chosen id is looked up in a
|
|
20
|
+
// catalog the model was shown, the arguments are validated by the shared descriptor validator, and
|
|
21
|
+
// the action resolves every name against the board itself. A model that hallucinates an action id,
|
|
22
|
+
// invents an argument key, or names a service that does not exist changes nothing: those are a
|
|
23
|
+
// decline, a dropped key and a question with candidates, in that order.
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* The reply budget for one routing decision.
|
|
27
|
+
*
|
|
28
|
+
* Small, because the deliverable is a two-field JSON object naming an id and copying a URL. It is
|
|
29
|
+
* not tight enough to truncate a legitimate answer, and a model that needs more than this is not
|
|
30
|
+
* answering the question it was asked.
|
|
31
|
+
*/
|
|
32
|
+
const MAX_OUTPUT_TOKENS = 800;
|
|
33
|
+
/**
|
|
34
|
+
* Routing must be reproducible: the same sentence against the same board has one right action, and
|
|
35
|
+
* a person who re-types a request that was declined needs the second attempt to differ because
|
|
36
|
+
* THEY changed something. Same value and same reasoning as the judge and bug-hunt assessments.
|
|
37
|
+
*/
|
|
38
|
+
const TEMPERATURE = 0;
|
|
39
|
+
export class AssistantService {
|
|
40
|
+
deps;
|
|
41
|
+
constructor(deps) {
|
|
42
|
+
this.deps = deps;
|
|
43
|
+
}
|
|
44
|
+
/** Whether a turn can run at all (some provider is wired), and what it could do. */
|
|
45
|
+
get enabled() {
|
|
46
|
+
return !!this.deps.modelProviderResolver || !!this.deps.modelProvider;
|
|
47
|
+
}
|
|
48
|
+
/** What this deployment's assistant offers, read before the prompt box is shown. */
|
|
49
|
+
capability() {
|
|
50
|
+
return {
|
|
51
|
+
available: this.enabled,
|
|
52
|
+
actions: this.deps.actions.map((action) => action.actionId),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Run one turn: route a sentence and perform what it named, or perform the ANSWER to the
|
|
57
|
+
* question the last turn asked.
|
|
58
|
+
*
|
|
59
|
+
* The two halves share everything after the action is known (argument validation, the run, the
|
|
60
|
+
* outcome) and nothing before it. An ANSWER reaches no model, which is not an optimisation: a
|
|
61
|
+
* clarification whose answer went back through the router could route somewhere else, and a
|
|
62
|
+
* question the platform asked deserves an answer the platform honours literally.
|
|
63
|
+
*/
|
|
64
|
+
async run(request) {
|
|
65
|
+
const { actions } = this.deps;
|
|
66
|
+
if (actions.length === 0) {
|
|
67
|
+
throw new UnavailableError('The assistant has no actions configured on this deployment', 'assistant_no_actions');
|
|
68
|
+
}
|
|
69
|
+
return request.input.kind === 'answer'
|
|
70
|
+
? this.answer(request, request.input.answer)
|
|
71
|
+
: this.route(request, request.input.prompt);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Perform what a previous turn's question was heading for, with the answered field filled in.
|
|
75
|
+
*
|
|
76
|
+
* No model, so no budget probe either: nothing here is billable. The arguments are the ones the
|
|
77
|
+
* platform itself put on that outcome, and they are re-validated anyway, so what a client may
|
|
78
|
+
* ask for is exactly what the prompt path could already have produced.
|
|
79
|
+
*/
|
|
80
|
+
async answer(request, answer) {
|
|
81
|
+
const action = this.deps.actions.find((candidate) => candidate.actionId === answer.actionId);
|
|
82
|
+
if (!action) {
|
|
83
|
+
// The deployment's catalog changed under a question it had already asked (an integration
|
|
84
|
+
// unwired between the two turns). Reported as the same decline a hallucinated id gets,
|
|
85
|
+
// because it means the same thing to the person: this deployment no longer does that.
|
|
86
|
+
return { outcome: { status: 'declined', reason: 'no_matching_action' }, model: null };
|
|
87
|
+
}
|
|
88
|
+
return { outcome: await this.perform(request, action, answer.arguments), model: null };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Read a sentence, name one action, perform it.
|
|
92
|
+
*
|
|
93
|
+
* The refusal ORDER is cheapest-first, on the same reading as the inline use-case surface: a
|
|
94
|
+
* request that was never going to run should spend nothing. A missing provider is answered from
|
|
95
|
+
* memory; the budget probe reads the spend ledger; only then does a vendor see a token.
|
|
96
|
+
*/
|
|
97
|
+
async route(request, prompt) {
|
|
98
|
+
const { modelProvider, ref } = await this.resolveModel(request.workspaceId);
|
|
99
|
+
if (await this.deps.isOverBudget?.(request.workspaceId)) {
|
|
100
|
+
// Its OWN refusal rather than a generic failure, and fail-CLOSED so no vendor call is made:
|
|
101
|
+
// an exhausted budget is not a broken assistant, and the fix (raise the budget, or wait for
|
|
102
|
+
// the window to roll) is not the fix for a misconfigured provider.
|
|
103
|
+
throw new RateLimitedError('This workspace has spent its configured model budget', 'budget_exhausted');
|
|
104
|
+
}
|
|
105
|
+
const model = { provider: ref.provider, model: ref.model };
|
|
106
|
+
const selection = readAssistantSelection(extractJson(await this.generate(request.workspaceId, prompt, modelProvider, ref)));
|
|
107
|
+
if (selection.kind === 'unreadable') {
|
|
108
|
+
// An empty visible reply means the model answered only into its private reasoning channel
|
|
109
|
+
// (seen on some reasoning models); a non-JSON one means it ignored the contract. Either way
|
|
110
|
+
// there is no routing decision, and acting on a guess is the one thing this surface must not
|
|
111
|
+
// do, so it says the reply could not be read rather than declining, which would tell the
|
|
112
|
+
// person their request was understood and rejected.
|
|
113
|
+
this.deps.logger?.warn('An assistant turn produced no readable routing decision', {
|
|
114
|
+
workspaceId: request.workspaceId,
|
|
115
|
+
model: `${ref.provider}:${ref.model}`,
|
|
116
|
+
});
|
|
117
|
+
throw new UnavailableError(`The assistant model (${ref.provider}:${ref.model}) returned no usable answer`, 'assistant_reply_unreadable');
|
|
118
|
+
}
|
|
119
|
+
if (selection.kind === 'none') {
|
|
120
|
+
return { outcome: { status: 'declined', reason: 'no_matching_action' }, model };
|
|
121
|
+
}
|
|
122
|
+
const action = this.deps.actions.find((candidate) => candidate.actionId === selection.actionId);
|
|
123
|
+
if (!action) {
|
|
124
|
+
// An id outside the catalog it was shown. Reported as "nothing here does that" rather than
|
|
125
|
+
// as a model failure, because that is what it means to the person: whatever the model
|
|
126
|
+
// thought it was naming, this deployment does not offer it.
|
|
127
|
+
this.deps.logger?.debug('An assistant turn named an unknown action', {
|
|
128
|
+
workspaceId: request.workspaceId,
|
|
129
|
+
action: selection.actionId,
|
|
130
|
+
});
|
|
131
|
+
return { outcome: { status: 'declined', reason: 'no_matching_action' }, model };
|
|
132
|
+
}
|
|
133
|
+
return { outcome: await this.perform(request, action, selection.arguments), model };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Everything between a NAMED action and the turn's outcome: validate, run, report.
|
|
137
|
+
*
|
|
138
|
+
* The one path both halves of `run` share, and deliberately so. A routed turn and an answered
|
|
139
|
+
* one differ only in where the arguments came from, and letting the answer skip a rule the
|
|
140
|
+
* router applies is how a second, weaker door gets built beside the first.
|
|
141
|
+
*/
|
|
142
|
+
async perform(request, action, supplied) {
|
|
143
|
+
const args = keepDeclaredArguments(action.parameters, supplied);
|
|
144
|
+
const invalid = firstInvalidArgument(action.parameters, args);
|
|
145
|
+
if (invalid) {
|
|
146
|
+
return clarification(action.actionId, args, {
|
|
147
|
+
status: 'needs_input',
|
|
148
|
+
reason: 'invalid_argument',
|
|
149
|
+
field: invalid,
|
|
150
|
+
candidates: [],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const outcome = await action.run({
|
|
154
|
+
workspaceId: request.workspaceId,
|
|
155
|
+
arguments: args,
|
|
156
|
+
editor: request.editor,
|
|
157
|
+
userId: request.userId,
|
|
158
|
+
});
|
|
159
|
+
return outcome.status === 'performed'
|
|
160
|
+
? { status: 'performed', result: outcome.result }
|
|
161
|
+
: clarification(action.actionId, args, outcome);
|
|
162
|
+
}
|
|
163
|
+
/** The one model call: the catalog and the request in, the raw reply out. */
|
|
164
|
+
async generate(workspaceId, prompt, modelProvider, ref) {
|
|
165
|
+
try {
|
|
166
|
+
const result = await generateText({
|
|
167
|
+
model: modelProvider.resolve(ref),
|
|
168
|
+
system: ASSISTANT_SYSTEM_PROMPT,
|
|
169
|
+
prompt: renderAssistantPrompt(assistantActionBriefs(this.deps.actions), prompt),
|
|
170
|
+
temperature: TEMPERATURE,
|
|
171
|
+
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
172
|
+
providerOptions: catFactoryObservability({
|
|
173
|
+
agentKind: ASSISTANT_AGENT_KIND,
|
|
174
|
+
workspaceId,
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
return result.text;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const message = `The assistant model (${ref.provider}:${ref.model}) failed: ${getErrorMessage(error)}`;
|
|
181
|
+
this.deps.logger?.warn(message, { workspaceId, ...describeError(error) });
|
|
182
|
+
throw new UnavailableError(message, 'assistant_generation_failed');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The provider and ref a turn runs on, or the 503 naming the deployment-level gap.
|
|
187
|
+
*
|
|
188
|
+
* An EMPTY selection, on the bug hunt's reading: a turn routes a sentence, not a task, so
|
|
189
|
+
* nothing pins a model or picks a preset and the workspace DEFAULT preset supplies both the
|
|
190
|
+
* model and the route order.
|
|
191
|
+
*/
|
|
192
|
+
async resolveModel(workspaceId) {
|
|
193
|
+
const modelProvider = await resolveScopedModelProvider({ workspaceId }, this.deps);
|
|
194
|
+
const ref = await resolveInlineBlockModelRef(this.deps, workspaceId, ASSISTANT_AGENT_KIND, {});
|
|
195
|
+
if (!modelProvider || !ref) {
|
|
196
|
+
throw new UnavailableError('No model is configured for the assistant', 'assistant_model_unavailable');
|
|
197
|
+
}
|
|
198
|
+
return { modelProvider, ref };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* A clarification as it goes on the wire: the action it was heading for, plus the arguments the
|
|
203
|
+
* turn resolved, so the next turn can answer it instead of re-reading a rewritten sentence.
|
|
204
|
+
*
|
|
205
|
+
* One place rather than two literals, because the ARGUMENTS are the half that is easy to forget
|
|
206
|
+
* and impossible to notice missing: the outcome still renders, the chips still appear, and every
|
|
207
|
+
* one of them answers into an empty argument bag.
|
|
208
|
+
*/
|
|
209
|
+
function clarification(actionId, args, outcome) {
|
|
210
|
+
return {
|
|
211
|
+
status: 'needs_input',
|
|
212
|
+
actionId,
|
|
213
|
+
reason: outcome.reason,
|
|
214
|
+
field: outcome.field,
|
|
215
|
+
candidates: outcome.candidates,
|
|
216
|
+
arguments: stringArguments(args),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The validated arguments as the wire shape, dropping any value that is not a string.
|
|
221
|
+
*
|
|
222
|
+
* `DescriptorFieldValues` admits booleans and numbers for the field types that declare them; the
|
|
223
|
+
* assistant's own parameters are all strings today, and a value with no string form is left OUT
|
|
224
|
+
* rather than stringified, because a `"false"` coming back as an answer would be a value nobody
|
|
225
|
+
* chose.
|
|
226
|
+
*/
|
|
227
|
+
function stringArguments(args) {
|
|
228
|
+
const out = {};
|
|
229
|
+
for (const [key, value] of Object.entries(args)) {
|
|
230
|
+
if (typeof value === 'string')
|
|
231
|
+
out[key] = value;
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* The first argument whose VALUE the shared descriptor rules refuse, or undefined.
|
|
237
|
+
*
|
|
238
|
+
* Field by field, over the keys the reply actually supplied, so the check reports WHICH argument
|
|
239
|
+
* is unusable, where the whole-bag validator answers with prose the SPA cannot localize and cannot
|
|
240
|
+
* point at a field with. Required-but-absent is deliberately not judged here: an argument the
|
|
241
|
+
* model omitted is the action's own question to ask, since only the action knows whether it can
|
|
242
|
+
* derive the value from something else it was given.
|
|
243
|
+
*/
|
|
244
|
+
function firstInvalidArgument(fields, values) {
|
|
245
|
+
for (const field of fields) {
|
|
246
|
+
const value = values[field.key];
|
|
247
|
+
if (value === undefined)
|
|
248
|
+
continue;
|
|
249
|
+
if (validateDescriptorFields([field], { [field.key]: value }).length > 0)
|
|
250
|
+
return field.key;
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=AssistantService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssistantService.js","sourceRoot":"","sources":["../../../src/modules/assistant/AssistantService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AACjC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,qBAAqB,CAAA;AAW5B,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AAEjE,OAAO,EACL,aAAa,EACb,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,0BAA0B,EAC1B,gBAAgB,GACjB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAA6B,0BAA0B,EAAE,MAAM,2BAA2B,CAAA;AACjG,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,sBAAsB,CAAA;AAG7B,8EAA8E;AAC9E,kGAAkG;AAClG,gBAAgB;AAChB,EAAE;AACF,8FAA8F;AAC9F,kGAAkG;AAClG,mGAAmG;AACnG,uFAAuF;AACvF,oEAAoE;AACpE,EAAE;AACF,kGAAkG;AAClG,mGAAmG;AACnG,6FAA6F;AAC7F,mGAAmG;AACnG,mGAAmG;AACnG,+FAA+F;AAC/F,wEAAwE;AACxE,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,iBAAiB,GAAG,GAAG,CAAA;AAE7B;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,CAAA;AAgCrB,MAAM,OAAO,gBAAgB;IACE,IAAI;IAAjC,YAA6B,IAA0B;oBAA1B,IAAI;IAAyB,CAAC;IAE3D,oFAAoF;IACpF,IAAI,OAAO;QACT,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAA;IACvE,CAAC;IAED,oFAAoF;IACpF,UAAU;QACR,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,OAAO;YACvB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC;SAC5D,CAAA;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAG,CAAC,OAA6B;QACrC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,CAAA;QAC7B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,gBAAgB,CACxB,4DAA4D,EAC5D,sBAAsB,CACvB,CAAA;QACH,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ;YACpC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;YAC5C,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAC/C,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,MAAM,CAClB,OAA6B,EAC7B,MAAuB;QAEvB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC5F,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,yFAAyF;YACzF,uFAAuF;YACvF,sFAAsF;YACtF,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QACvF,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IACxF,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,KAAK,CAAC,OAA6B,EAAE,MAAc;QAC/D,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;QAC3E,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACxD,4FAA4F;YAC5F,4FAA4F;YAC5F,mEAAmE;YACnE,MAAM,IAAI,gBAAgB,CACxB,sDAAsD,EACtD,kBAAkB,CACnB,CAAA;QACH,CAAC;QAED,MAAM,KAAK,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAA;QAC1D,MAAM,SAAS,GAAG,sBAAsB,CACtC,WAAW,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC,CAClF,CAAA;QACD,IAAI,SAAS,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACpC,0FAA0F;YAC1F,4FAA4F;YAC5F,6FAA6F;YAC7F,yFAAyF;YACzF,oDAAoD;YACpD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,yDAAyD,EAAE;gBAChF,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,KAAK,EAAE,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,KAAK,EAAE;aACtC,CAAC,CAAA;YACF,MAAM,IAAI,gBAAgB,CACxB,wBAAwB,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,KAAK,6BAA6B,EAC9E,4BAA4B,CAC7B,CAAA;QACH,CAAC;QACD,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC9B,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,CAAA;QACjF,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,KAAK,SAAS,CAAC,QAAQ,CAAC,CAAA;QAC/F,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,2FAA2F;YAC3F,sFAAsF;YACtF,4DAA4D;YAC5D,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,2CAA2C,EAAE;gBACnE,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,MAAM,EAAE,SAAS,CAAC,QAAQ;aAC3B,CAAC,CAAA;YACF,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,CAAA;QACjF,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAA;IACrF,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,OAAO,CACnB,OAA6B,EAC7B,MAAiC,EACjC,QAAgC;QAEhC,MAAM,IAAI,GAAG,qBAAqB,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;QAC/D,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;QAC7D,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,aAAa,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE;gBAC1C,MAAM,EAAE,aAAa;gBACrB,MAAM,EAAE,kBAAkB;gBAC1B,KAAK,EAAE,OAAO;gBACd,UAAU,EAAE,EAAE;aACf,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC;YAC/B,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAA;QACF,OAAO,OAAO,CAAC,MAAM,KAAK,WAAW;YACnC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;YACjD,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IACnD,CAAC;IAED,6EAA6E;IACrE,KAAK,CAAC,QAAQ,CACpB,WAAmB,EACnB,MAAc,EACd,aAA4B,EAC5B,GAAa;QAEb,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;gBAChC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC;gBACjC,MAAM,EAAE,uBAAuB;gBAC/B,MAAM,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;gBAC/E,WAAW,EAAE,WAAW;gBACxB,eAAe,EAAE,iBAAiB;gBAClC,eAAe,EAAE,uBAAuB,CAAC;oBACvC,SAAS,EAAE,oBAAoB;oBAC/B,WAAW;iBACZ,CAAC;aACH,CAAC,CAAA;YACF,OAAO,MAAM,CAAC,IAAI,CAAA;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,wBAAwB,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,KAAK,aAAa,eAAe,CAAC,KAAK,CAAC,EAAE,CAAA;YACtG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACzE,MAAM,IAAI,gBAAgB,CAAC,OAAO,EAAE,6BAA6B,CAAC,CAAA;QACpE,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,YAAY,CACxB,WAAmB;QAEnB,MAAM,aAAa,GAAG,MAAM,0BAA0B,CAAC,EAAE,WAAW,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAClF,MAAM,GAAG,GAAG,MAAM,0BAA0B,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,oBAAoB,EAAE,EAAE,CAAC,CAAA;QAC9F,IAAI,CAAC,aAAa,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,MAAM,IAAI,gBAAgB,CACxB,0CAA0C,EAC1C,6BAA6B,CAC9B,CAAA;QACH,CAAC;QACD,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,CAAA;IAC/B,CAAC;CACF;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CACpB,QAA2B,EAC3B,IAA2B,EAC3B,OAAmE;IAEnE,OAAO;QACL,MAAM,EAAE,aAAa;QACrB,QAAQ;QACR,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,SAAS,EAAE,eAAe,CAAC,IAAI,CAAC;KACjC,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,IAA2B;IAClD,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;IACjD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAC3B,MAAkC,EAClC,MAA6B;IAE7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,KAAK,KAAK,SAAS;YAAE,SAAQ;QACjC,IAAI,wBAAwB,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC,GAAG,CAAA;IAC5F,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { AddServiceFromRepoInput, Block, BlockEditAuthority, GitHubRepo, SourceTask, TaskSourceKind, UpdateBlockInput } from '@cat-factory/contracts';
|
|
2
|
+
import type { Service } from '@cat-factory/kernel';
|
|
3
|
+
import type { AddedServiceFrame } from '../../board/serviceRepoLinkage.js';
|
|
4
|
+
/** Board reads + writes every action shares. */
|
|
5
|
+
export interface AssistantBoardDeps {
|
|
6
|
+
/**
|
|
7
|
+
* The board's blocks AS THE PERSON SEES THEM, mounted shared services included.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately the composed board rather than the workspace's own rows: a service another team
|
|
10
|
+
* homes and this board mounts is on screen, is nameable in a sentence, and is a legitimate end
|
|
11
|
+
* of a dependency edge. Resolving names against the narrower read would answer "there is no
|
|
12
|
+
* such service" about a frame the person is looking at.
|
|
13
|
+
*/
|
|
14
|
+
listBoardBlocks(workspaceId: string): Promise<Block[]>;
|
|
15
|
+
/** Apply a patch to a block, under the asker's own tier. */
|
|
16
|
+
updateBlock(workspaceId: string, id: string, patch: UpdateBlockInput, editor: BlockEditAuthority): Promise<Block>;
|
|
17
|
+
}
|
|
18
|
+
/** What the repository-backed service action needs. Absent ⇒ no VCS integration is wired. */
|
|
19
|
+
export interface AssistantRepoDeps {
|
|
20
|
+
/** The repositories this workspace projects, for resolving a pasted URL to one. */
|
|
21
|
+
listRepos(workspaceId: string): Promise<GitHubRepo[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Create (or mount) the service frame backed by a projected repository.
|
|
24
|
+
*
|
|
25
|
+
* Answers with the DISPOSITION beside the frame, because the two are different facts about the
|
|
26
|
+
* board and the turn reports which one happened. Reading it off the returned block is not
|
|
27
|
+
* possible: a first-time mount answers with a frame this board has never held, which is exactly
|
|
28
|
+
* what a fresh create looks like from the outside.
|
|
29
|
+
*/
|
|
30
|
+
addServiceFromRepo(workspaceId: string, input: AddServiceFromRepoInput): Promise<AddedServiceFrame>;
|
|
31
|
+
/** The account-owned services behind a set of frames, for the repo → frame linkage. */
|
|
32
|
+
listServicesForFrames(frameBlockIds: string[]): Promise<Service[]>;
|
|
33
|
+
}
|
|
34
|
+
/** One connected tracker that recognises a pasted issue URL. */
|
|
35
|
+
export interface AssistantIssueMatch {
|
|
36
|
+
source: TaskSourceKind;
|
|
37
|
+
/** The source's canonical key for the issue, as its own provider parsed it. */
|
|
38
|
+
externalId: string;
|
|
39
|
+
}
|
|
40
|
+
/** What the tracker-issue action needs. Absent ⇒ no task source is wired. */
|
|
41
|
+
export interface AssistantIssueDeps {
|
|
42
|
+
/**
|
|
43
|
+
* Every ENABLED task source whose provider recognises this reference.
|
|
44
|
+
*
|
|
45
|
+
* Plural on purpose: which tracker a URL belongs to is a judgement only the providers can make,
|
|
46
|
+
* and two of them recognising the same string is a real state (a bare `PROJ-12` is both a Jira
|
|
47
|
+
* key and a Linear identifier). The action reports that as a question rather than picking, so
|
|
48
|
+
* the ordering of the registry can never decide which tracker a person's issue came from.
|
|
49
|
+
*/
|
|
50
|
+
matchIssueSources(workspaceId: string, ref: string): Promise<AssistantIssueMatch[]>;
|
|
51
|
+
/** Fetch the issue and upsert its local projection. */
|
|
52
|
+
importIssue(workspaceId: string, source: TaskSourceKind, ref: string): Promise<SourceTask>;
|
|
53
|
+
/** Materialise an imported issue as a board task inside a container, linked to the issue. */
|
|
54
|
+
createTaskFromIssue(input: {
|
|
55
|
+
workspaceId: string;
|
|
56
|
+
containerId: string;
|
|
57
|
+
source: TaskSourceKind;
|
|
58
|
+
externalId: string;
|
|
59
|
+
editor: BlockEditAuthority;
|
|
60
|
+
createdBy: string | null;
|
|
61
|
+
}): Promise<{
|
|
62
|
+
block: Block;
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
65
|
+
/** Everything the built-in catalog is assembled from. */
|
|
66
|
+
export interface AssistantActionDeps {
|
|
67
|
+
board: AssistantBoardDeps;
|
|
68
|
+
repos?: AssistantRepoDeps;
|
|
69
|
+
issues?: AssistantIssueDeps;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=deps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/deps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,KAAK,EACL,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,cAAc,EACd,gBAAgB,EACjB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAiB1E,gDAAgD;AAChD,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;IACtD,4DAA4D;IAC5D,WAAW,CACT,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,KAAK,CAAC,CAAA;CAClB;AAED,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IACrD;;;;;;;OAOG;IACH,kBAAkB,CAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC7B,uFAAuF;IACvF,qBAAqB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;CACnE;AAED,gEAAgE;AAChE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAA;IACtB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAA;IACnF,uDAAuD;IACvD,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IAC1F,6FAA6F;IAC7F,mBAAmB,CAAC,KAAK,EAAE;QACzB,WAAW,EAAE,MAAM,CAAA;QACnB,WAAW,EAAE,MAAM,CAAA;QACnB,MAAM,EAAE,cAAc,CAAA;QACtB,UAAU,EAAE,MAAM,CAAA;QAClB,MAAM,EAAE,kBAAkB,CAAA;QAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KACzB,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;CAC9B;AAED,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,kBAAkB,CAAA;IACzB,KAAK,CAAC,EAAE,iBAAiB,CAAA;IACzB,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps.js","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/deps.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AssistantActionDefinition } from '../types.js';
|
|
2
|
+
import type { AssistantActionDeps } from './deps.js';
|
|
3
|
+
export type { AssistantActionDeps, AssistantBoardDeps, AssistantIssueDeps, AssistantIssueMatch, AssistantRepoDeps, } from './deps.js';
|
|
4
|
+
/**
|
|
5
|
+
* The built-in action catalog, in the order the model is shown it and the SPA suggests it.
|
|
6
|
+
*
|
|
7
|
+
* An action whose integration a deployment did not wire is OMITTED rather than registered and left
|
|
8
|
+
* to fail: the catalog is what the model routes against, so an unwired action is one the model can
|
|
9
|
+
* still choose, and the turn would then answer a legitimate request with an internal-sounding
|
|
10
|
+
* refusal. Omitted, the same request declines with "nothing in the catalog does that", which is
|
|
11
|
+
* both true of this deployment and what its `available` capability read already said.
|
|
12
|
+
*
|
|
13
|
+
* The catalog is a plain list rather than a registry: every member performs a board write through
|
|
14
|
+
* an engine-internal service, which is the same reason the `merger` step resolver is a privileged
|
|
15
|
+
* built-in rather than a registry entry. Opening it to deployment-registered actions means giving
|
|
16
|
+
* an action a public, minimal context first, and that is a design decision of its own. See
|
|
17
|
+
* `docs/initiatives/in-app-assistant.md`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createAssistantActions(deps: AssistantActionDeps): AssistantActionDefinition[];
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAKpD,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,WAAW,CAAA;AAElB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,yBAAyB,EAAE,CAO7F"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { addServiceFromRepoAction } from './serviceFromRepo.js';
|
|
2
|
+
import { createTaskFromIssueAction } from './taskFromIssue.js';
|
|
3
|
+
import { declareServiceDependencyAction } from './serviceDependency.js';
|
|
4
|
+
/**
|
|
5
|
+
* The built-in action catalog, in the order the model is shown it and the SPA suggests it.
|
|
6
|
+
*
|
|
7
|
+
* An action whose integration a deployment did not wire is OMITTED rather than registered and left
|
|
8
|
+
* to fail: the catalog is what the model routes against, so an unwired action is one the model can
|
|
9
|
+
* still choose, and the turn would then answer a legitimate request with an internal-sounding
|
|
10
|
+
* refusal. Omitted, the same request declines with "nothing in the catalog does that", which is
|
|
11
|
+
* both true of this deployment and what its `available` capability read already said.
|
|
12
|
+
*
|
|
13
|
+
* The catalog is a plain list rather than a registry: every member performs a board write through
|
|
14
|
+
* an engine-internal service, which is the same reason the `merger` step resolver is a privileged
|
|
15
|
+
* built-in rather than a registry entry. Opening it to deployment-registered actions means giving
|
|
16
|
+
* an action a public, minimal context first, and that is a design decision of its own. See
|
|
17
|
+
* `docs/initiatives/in-app-assistant.md`.
|
|
18
|
+
*/
|
|
19
|
+
export function createAssistantActions(deps) {
|
|
20
|
+
const actions = [declareServiceDependencyAction(deps.board)];
|
|
21
|
+
if (deps.repos)
|
|
22
|
+
actions.push(addServiceFromRepoAction(deps.repos));
|
|
23
|
+
if (deps.issues) {
|
|
24
|
+
actions.push(createTaskFromIssueAction(deps.board, deps.issues, deps.repos));
|
|
25
|
+
}
|
|
26
|
+
return actions;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;AAC9D,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAA;AAUvE;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAyB;IAC9D,MAAM,OAAO,GAAgC,CAAC,8BAA8B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IACzF,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IAClE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IAC9E,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AssistantActionDefinition } from '../types.js';
|
|
2
|
+
import type { AssistantBoardDeps } from './deps.js';
|
|
3
|
+
export declare function declareServiceDependencyAction(deps: AssistantBoardDeps): AssistantActionDefinition;
|
|
4
|
+
//# sourceMappingURL=serviceDependency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceDependency.d.ts","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/serviceDependency.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAA0B,MAAM,aAAa,CAAA;AAEpF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAuCnD,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,kBAAkB,GACvB,yBAAyB,CAgE3B"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { matchServiceByName, serviceFramesOf } from '../assistant.logic.js';
|
|
2
|
+
import { needsInput, performed } from '../types.js';
|
|
3
|
+
import { readArgument, resolveNamedService } from './shared.js';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// "Service A depends on service B": the assistant's route to the connection edge a service frame
|
|
6
|
+
// already carries (`service-connections.ts`), which is what makes both services spin up when a
|
|
7
|
+
// task on either is tested and what folds the relationship into the agents' prompts.
|
|
8
|
+
//
|
|
9
|
+
// The edge is stored on the CONSUMER, so which service is which is not a presentation detail: the
|
|
10
|
+
// two arguments are named `consumer` and `provider` and described to the model in terms of what
|
|
11
|
+
// depends on what, rather than as a first and second service it could fill in either order.
|
|
12
|
+
//
|
|
13
|
+
// Nothing here restates a board rule. A self-edge, a target that is not a service frame and a
|
|
14
|
+
// duplicate are all refused by `updateBlock`'s own validation, and they arrive at the SPA as the
|
|
15
|
+
// same 422 the inspector's picker would raise. The one thing this owns is IDEMPOTENCE: asking
|
|
16
|
+
// twice for an edge that exists is a request the board is already satisfying, so it reports the
|
|
17
|
+
// state rather than writing, and says which of the two happened.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
const PARAMETERS = [
|
|
20
|
+
{
|
|
21
|
+
key: 'consumer',
|
|
22
|
+
label: 'Consumer service',
|
|
23
|
+
help: 'The name of the service that DEPENDS on the other one, the one whose work needs the other running.',
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: 'provider',
|
|
28
|
+
label: 'Provider service',
|
|
29
|
+
help: 'The name of the service that is depended ON, and is spun up alongside the consumer when it is tested.',
|
|
30
|
+
required: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: 'description',
|
|
34
|
+
label: 'How it is used',
|
|
35
|
+
help: 'Optional one-line description of how the consumer uses the provider, e.g. "sends transactional email via it".',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
export function declareServiceDependencyAction(deps) {
|
|
39
|
+
return {
|
|
40
|
+
actionId: 'declare-service-dependency',
|
|
41
|
+
purpose: 'Record that one service on the board depends on another, so the provider is started ' +
|
|
42
|
+
'alongside the consumer when the consumer is tested, and agents are told how it is used.',
|
|
43
|
+
examples: [
|
|
44
|
+
'the checkout service depends on the payments service for authorising cards',
|
|
45
|
+
'make notifications a dependency of the orders service',
|
|
46
|
+
],
|
|
47
|
+
parameters: PARAMETERS,
|
|
48
|
+
async run({ workspaceId, arguments: args, editor }) {
|
|
49
|
+
const consumerName = readArgument(args, 'consumer');
|
|
50
|
+
if (consumerName === undefined)
|
|
51
|
+
return needsInput('missing_argument', 'consumer');
|
|
52
|
+
const providerName = readArgument(args, 'provider');
|
|
53
|
+
if (providerName === undefined)
|
|
54
|
+
return needsInput('missing_argument', 'provider');
|
|
55
|
+
// ONE board read for both ends: the two names are matched against the same snapshot, so a
|
|
56
|
+
// frame renamed between them cannot make the pair resolve against two different boards.
|
|
57
|
+
const frames = serviceFramesOf(await deps.listBoardBlocks(workspaceId));
|
|
58
|
+
const consumer = resolveNamedService(matchServiceByName(frames, consumerName), 'consumer');
|
|
59
|
+
if (consumer.kind !== 'one')
|
|
60
|
+
return consumer.outcome;
|
|
61
|
+
const provider = resolveNamedService(matchServiceByName(frames, providerName), 'provider');
|
|
62
|
+
if (provider.kind !== 'one')
|
|
63
|
+
return provider.outcome;
|
|
64
|
+
const description = readArgument(args, 'description');
|
|
65
|
+
const existing = consumer.frame.serviceConnections ?? [];
|
|
66
|
+
const declared = existing.find((edge) => edge.serviceBlockId === provider.frame.id);
|
|
67
|
+
const ref = (frame) => ({
|
|
68
|
+
blockId: frame.id,
|
|
69
|
+
title: frame.title,
|
|
70
|
+
});
|
|
71
|
+
if (declared && (description === undefined || declared.description === description)) {
|
|
72
|
+
// Already exactly as asked. Reported as a success that CREATED nothing rather than as a
|
|
73
|
+
// conflict: the board is in the requested state, and a person who asked twice wants to
|
|
74
|
+
// know that, not to be refused.
|
|
75
|
+
return performed({
|
|
76
|
+
actionId: 'declare-service-dependency',
|
|
77
|
+
consumer: ref(consumer.frame),
|
|
78
|
+
provider: ref(provider.frame),
|
|
79
|
+
created: false,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const edge = {
|
|
83
|
+
serviceBlockId: provider.frame.id,
|
|
84
|
+
...(description === undefined ? {} : { description }),
|
|
85
|
+
};
|
|
86
|
+
const next = declared
|
|
87
|
+
? existing.map((entry) => (entry.serviceBlockId === provider.frame.id ? edge : entry))
|
|
88
|
+
: [...existing, edge];
|
|
89
|
+
const updated = await deps.updateBlock(workspaceId, consumer.frame.id, { serviceConnections: next }, editor);
|
|
90
|
+
return performed({
|
|
91
|
+
actionId: 'declare-service-dependency',
|
|
92
|
+
consumer: { blockId: updated.id, title: updated.title },
|
|
93
|
+
provider: ref(provider.frame),
|
|
94
|
+
created: !declared,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=serviceDependency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceDependency.js","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/serviceDependency.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceFromRepo.d.ts","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/serviceFromRepo.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAA0B,MAAM,aAAa,CAAA;AAEpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAoDlD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,iBAAiB,GAAG,yBAAyB,CAuD3F"}
|