@cat-factory/orchestration 0.305.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/package.json +11 -11
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { parseRepoRef } from '../assistant.logic.js';
|
|
2
|
+
import { needsInput, performed } from '../types.js';
|
|
3
|
+
import { readArgument } from './shared.js';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// "Add <this repository> as a service": the assistant's route to the board's own
|
|
6
|
+
// add-service-from-repo import, addressed by the URL a person has in their clipboard instead of by
|
|
7
|
+
// the numeric provider id the API takes.
|
|
8
|
+
//
|
|
9
|
+
// The URL is parsed by the SHARED parser (`repo-url.ts`), the same one the repository picker
|
|
10
|
+
// resolves a pasted URL with, so GitHub and GitLab shapes (subgroups, `/-/`, `/tree/<ref>/<path>`)
|
|
11
|
+
// are understood identically on both surfaces and a self-managed host is not a special case. A
|
|
12
|
+
// bare `owner/name` slug is accepted beside it, because that is the shape of the near-misses this
|
|
13
|
+
// action itself offers when a URL names a repository the workspace cannot see (`parseRepoRef`).
|
|
14
|
+
//
|
|
15
|
+
// The repository must already be PROJECTED for this workspace. That is not a shortcut: the
|
|
16
|
+
// projection is what the workspace's VCS connection can reach, so a URL that is not in it names
|
|
17
|
+
// either a repository the deployment has no credential for or one whose connection has not synced
|
|
18
|
+
// and inventing a link for it would create a service frame whose every run fails at clone time.
|
|
19
|
+
// The turn says which repository it could not find instead, with the near-misses by name.
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const PARAMETERS = [
|
|
22
|
+
{
|
|
23
|
+
key: 'repoUrl',
|
|
24
|
+
label: 'Repository URL',
|
|
25
|
+
help: 'The web URL of the GitHub or GitLab repository, copied verbatim, e.g. https://github.com/acme/payments. An owner/name slug such as acme/payments is accepted too.',
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: 'directory',
|
|
30
|
+
label: 'Subdirectory',
|
|
31
|
+
type: 'path',
|
|
32
|
+
help: 'For a monorepo only: the path inside the repository this service lives in, e.g. packages/api. Omit for a whole-repo service.',
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Repositories whose NAME matches, under any owner: what a wrong-owner URL most often meant.
|
|
37
|
+
*
|
|
38
|
+
* `owner/name` slugs, and that shape is load-bearing rather than cosmetic: they are offered as the
|
|
39
|
+
* ANSWER to "which repository did you mean?", so each one has to be a value this action's own
|
|
40
|
+
* `repoUrl` argument accepts (see `parseRepoRef`). A candidate the next turn would refuse as
|
|
41
|
+
* unreadable is a question with no acceptable answer.
|
|
42
|
+
*/
|
|
43
|
+
function nearMisses(repos, name, limit = 5) {
|
|
44
|
+
const wanted = name.toLowerCase();
|
|
45
|
+
return repos
|
|
46
|
+
.filter((repo) => repo.name.toLowerCase() === wanted)
|
|
47
|
+
.slice(0, limit)
|
|
48
|
+
.map((repo) => `${repo.owner}/${repo.name}`);
|
|
49
|
+
}
|
|
50
|
+
export function addServiceFromRepoAction(deps) {
|
|
51
|
+
return {
|
|
52
|
+
actionId: 'add-service-from-repo',
|
|
53
|
+
purpose: 'Put a service on the board backed by an existing GitHub or GitLab repository, named by ' +
|
|
54
|
+
'its web URL, so tasks can be filed against it and agents work on a real checkout of it.',
|
|
55
|
+
examples: [
|
|
56
|
+
'add https://github.com/acme/payments as a service',
|
|
57
|
+
'put the repo https://gitlab.com/acme/platform/billing on the board',
|
|
58
|
+
'add packages/api from https://github.com/acme/monorepo as a service',
|
|
59
|
+
],
|
|
60
|
+
parameters: PARAMETERS,
|
|
61
|
+
async run({ workspaceId, arguments: args }) {
|
|
62
|
+
const url = readArgument(args, 'repoUrl');
|
|
63
|
+
if (url === undefined)
|
|
64
|
+
return needsInput('missing_argument', 'repoUrl');
|
|
65
|
+
const parsed = parseRepoRef(url);
|
|
66
|
+
if (!parsed)
|
|
67
|
+
return needsInput('unreadable_repository_url', 'repoUrl');
|
|
68
|
+
const repos = await deps.listRepos(workspaceId);
|
|
69
|
+
const repo = repos.find((candidate) => candidate.owner.toLowerCase() === parsed.owner.toLowerCase() &&
|
|
70
|
+
candidate.name.toLowerCase() === parsed.repo.toLowerCase());
|
|
71
|
+
if (!repo) {
|
|
72
|
+
return needsInput('unknown_repository', 'repoUrl', nearMisses(repos, parsed.repo));
|
|
73
|
+
}
|
|
74
|
+
// The subdirectory comes from the argument when the person named one, else from the
|
|
75
|
+
// reference itself when it points INTO the tree (`/tree/<ref>/packages/api`). A URL that
|
|
76
|
+
// deep is a statement about which part of the repository is meant, and dropping it would
|
|
77
|
+
// silently create a whole-repo service for a request that named a subtree. Both have been
|
|
78
|
+
// through the shared path rule by the time they get here: the stated one through the turn's
|
|
79
|
+
// own descriptor validation, the recovered one inside `parseRepoRef`.
|
|
80
|
+
const directory = readArgument(args, 'directory') ?? parsed.directory;
|
|
81
|
+
// `created` comes from the board service's own DISPOSITION, never from comparing the
|
|
82
|
+
// returned frame against the board as it stood a moment ago. That comparison was wrong in
|
|
83
|
+
// the one case the flag exists for: the account-wide dedupe answers with the frame of a
|
|
84
|
+
// service homed on ANOTHER board, which was never among this board's blocks, so a
|
|
85
|
+
// first-time mount read as a fresh create. Only the operation knows which path it took.
|
|
86
|
+
const { block, disposition } = await deps.addServiceFromRepo(workspaceId, {
|
|
87
|
+
repoGithubId: repo.githubId,
|
|
88
|
+
// The monorepo flag rides the add request, as it does from the import modal: naming a
|
|
89
|
+
// subdirectory IS the statement that the repository hosts more than one service.
|
|
90
|
+
...(directory === undefined ? {} : { directory, isMonorepo: true }),
|
|
91
|
+
});
|
|
92
|
+
return performed({
|
|
93
|
+
actionId: 'add-service-from-repo',
|
|
94
|
+
service: { blockId: block.id, title: block.title },
|
|
95
|
+
repo: { owner: repo.owner, name: repo.name, directory: directory ?? null },
|
|
96
|
+
created: disposition === 'created',
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=serviceFromRepo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceFromRepo.js","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/serviceFromRepo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DescriptorFieldValues } from '@cat-factory/contracts';
|
|
2
|
+
import type { Block } from '@cat-factory/kernel';
|
|
3
|
+
import type { ServiceMatch } from '../assistant.logic.js';
|
|
4
|
+
import type { AssistantActionOutcome } from '../types.js';
|
|
5
|
+
/**
|
|
6
|
+
* One argument as a non-empty string, or undefined when the model omitted it.
|
|
7
|
+
*
|
|
8
|
+
* The descriptor validator has already refused a value of the wrong TYPE, so this narrows rather
|
|
9
|
+
* than validates; a blank string is treated as absent because a model that emits `""` for a field
|
|
10
|
+
* it had nothing to say about has, in substance, omitted it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readArgument(args: DescriptorFieldValues, key: string): string | undefined;
|
|
13
|
+
/** A resolved frame, or the clarification the turn answers with instead. */
|
|
14
|
+
export type ResolvedService = {
|
|
15
|
+
kind: 'one';
|
|
16
|
+
frame: Block;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'unresolved';
|
|
19
|
+
outcome: AssistantActionOutcome;
|
|
20
|
+
};
|
|
21
|
+
/** Turn a name match into either the frame or the question to ask about `field`. */
|
|
22
|
+
export declare function resolveNamedService(match: ServiceMatch, field: string): ResolvedService;
|
|
23
|
+
/**
|
|
24
|
+
* The board's service titles, as the shortlist a clarification offers.
|
|
25
|
+
*
|
|
26
|
+
* Capped, because the list is rendered inside a question: a board with eighty services would
|
|
27
|
+
* answer "which one?" with a wall nobody reads, and the cap is what keeps the question askable.
|
|
28
|
+
* The names are the person's own, so there is nothing to sanitize beyond the truncation.
|
|
29
|
+
*/
|
|
30
|
+
export declare function serviceTitles(frames: readonly Block[], limit?: number): string[];
|
|
31
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAMzD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKzF;AAED,4EAA4E;AAC5E,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,sBAAsB,CAAA;CAAE,CAAA;AAE3D,oFAAoF;AACpF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,CAMvF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,KAAK,SAAI,GAAG,MAAM,EAAE,CAE3E"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { needsInput } from '../types.js';
|
|
2
|
+
// Two readings every built-in action repeats: an argument the model may simply not have supplied,
|
|
3
|
+
// and a service name that resolved to one frame, none, or several.
|
|
4
|
+
/**
|
|
5
|
+
* One argument as a non-empty string, or undefined when the model omitted it.
|
|
6
|
+
*
|
|
7
|
+
* The descriptor validator has already refused a value of the wrong TYPE, so this narrows rather
|
|
8
|
+
* than validates; a blank string is treated as absent because a model that emits `""` for a field
|
|
9
|
+
* it had nothing to say about has, in substance, omitted it.
|
|
10
|
+
*/
|
|
11
|
+
export function readArgument(args, key) {
|
|
12
|
+
const value = args[key];
|
|
13
|
+
if (typeof value !== 'string')
|
|
14
|
+
return undefined;
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
return trimmed === '' ? undefined : trimmed;
|
|
17
|
+
}
|
|
18
|
+
/** Turn a name match into either the frame or the question to ask about `field`. */
|
|
19
|
+
export function resolveNamedService(match, field) {
|
|
20
|
+
if (match.kind === 'one')
|
|
21
|
+
return { kind: 'one', frame: match.frame };
|
|
22
|
+
if (match.kind === 'many') {
|
|
23
|
+
return { kind: 'unresolved', outcome: needsInput('ambiguous_service', field, match.candidates) };
|
|
24
|
+
}
|
|
25
|
+
return { kind: 'unresolved', outcome: needsInput('unknown_service', field) };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The board's service titles, as the shortlist a clarification offers.
|
|
29
|
+
*
|
|
30
|
+
* Capped, because the list is rendered inside a question: a board with eighty services would
|
|
31
|
+
* answer "which one?" with a wall nobody reads, and the cap is what keeps the question askable.
|
|
32
|
+
* The names are the person's own, so there is nothing to sanitize beyond the truncation.
|
|
33
|
+
*/
|
|
34
|
+
export function serviceTitles(frames, limit = 8) {
|
|
35
|
+
return frames.slice(0, limit).map((frame) => frame.title);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/shared.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,kGAAkG;AAClG,mEAAmE;AAEnE;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAA2B,EAAE,GAAW;IACnE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;IACvB,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IAC5B,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAA;AAC7C,CAAC;AAOD,oFAAoF;AACpF,MAAM,UAAU,mBAAmB,CAAC,KAAmB,EAAE,KAAa;IACpE,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAA;IACpE,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,CAAC,mBAAmB,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,CAAA;IAClG,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,CAAC,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAA;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAwB,EAAE,KAAK,GAAG,CAAC;IAC/D,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AAC3D,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AssistantActionDefinition } from '../types.js';
|
|
2
|
+
import type { AssistantBoardDeps, AssistantIssueDeps, AssistantRepoDeps } from './deps.js';
|
|
3
|
+
export declare function createTaskFromIssueAction(board: AssistantBoardDeps, deps: AssistantIssueDeps, repos: AssistantRepoDeps | undefined): AssistantActionDefinition;
|
|
4
|
+
//# sourceMappingURL=taskFromIssue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taskFromIssue.d.ts","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/taskFromIssue.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,yBAAyB,EAA0B,MAAM,aAAa,CAAA;AAEpF,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAkF1F,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,kBAAkB,EACzB,IAAI,EAAE,kBAAkB,EACxB,KAAK,EAAE,iBAAiB,GAAG,SAAS,GACnC,yBAAyB,CA2E3B"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { issueRepoSlug, matchServiceByName, serviceFramesOf } from '../assistant.logic.js';
|
|
2
|
+
import { needsInput, performed } from '../types.js';
|
|
3
|
+
import { readArgument, resolveNamedService, serviceTitles } from './shared.js';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// "File this issue as a task": the assistant's route to the board's own create-task-from-issue
|
|
6
|
+
// flow, addressed by the URL a person is looking at rather than by a source id plus an external
|
|
7
|
+
// key plus a container id.
|
|
8
|
+
//
|
|
9
|
+
// The turn resolves three things the API takes as given, and each one is computed rather than
|
|
10
|
+
// guessed:
|
|
11
|
+
//
|
|
12
|
+
// - WHICH TRACKER the URL belongs to is answered by the providers themselves, by asking every
|
|
13
|
+
// OFFERED source to parse it. Two answers is a question, never a pick (see `matchIssueSources`),
|
|
14
|
+
// asked about the `source` argument: a request may state it outright ("file PROJ-7 from Jira")
|
|
15
|
+
// and an answered clarification fills it in. It only ever BREAKS A TIE among the providers'
|
|
16
|
+
// own answers, so naming a tracker cannot conjure one that does not read the reference.
|
|
17
|
+
// - WHICH SERVICE the task is filed under is the one whose linked repository the issue lives in,
|
|
18
|
+
// when the issue is repo-backed and exactly one service is linked to that repository. Anything
|
|
19
|
+
// else (a Jira ticket with no repository in its URL, a monorepo repository backing several
|
|
20
|
+
// services, a repository on no board frame) is a question with the board's services attached.
|
|
21
|
+
// - The ISSUE ITSELF is imported first, because the task's title and description come from it
|
|
22
|
+
// and because an unreachable issue must fail before a block exists rather than after.
|
|
23
|
+
//
|
|
24
|
+
// The heavy refusals stay where they already live: a disabled or unconfigured source and an issue
|
|
25
|
+
// already filed as a task are both raised by the services underneath, and reach the SPA as the
|
|
26
|
+
// same errors the import modal produces.
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
const PARAMETERS = [
|
|
29
|
+
{
|
|
30
|
+
key: 'issueUrl',
|
|
31
|
+
label: 'Issue URL',
|
|
32
|
+
help: 'The web URL of the issue or ticket, copied verbatim, e.g. https://github.com/acme/api/issues/12 or https://acme.atlassian.net/browse/PROJ-7.',
|
|
33
|
+
required: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'service',
|
|
37
|
+
label: 'Service',
|
|
38
|
+
help: 'The name of the service to file the task under. Omit it when the request does not say, and the platform uses the service backed by the issue’s own repository.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: 'source',
|
|
42
|
+
label: 'Tracker',
|
|
43
|
+
help: 'Which tracker the issue lives in, e.g. jira, linear, github, gitlab. Only worth stating when the reference could belong to more than one, such as a bare ticket key. Omit it and the connected trackers are asked which of them recognises the reference.',
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* The service frame backed by the repository the issue lives in, or null when that cannot be
|
|
48
|
+
* decided from the board alone.
|
|
49
|
+
*
|
|
50
|
+
* Null covers three genuinely different states, and they are collapsed ON PURPOSE at this level:
|
|
51
|
+
* a tracker with no repository at all (Jira, Linear), a repository this workspace does not
|
|
52
|
+
* project, and a repository backing SEVERAL services (a monorepo, where the link cannot say which
|
|
53
|
+
* subdirectory an issue belongs to). The caller's next move is the same question in all three
|
|
54
|
+
* ("which service?"), and the shortlist it offers is the board's services either way.
|
|
55
|
+
*/
|
|
56
|
+
async function serviceForIssueRepo(repos, workspaceId, frames, externalId) {
|
|
57
|
+
const parsed = issueRepoSlug(externalId);
|
|
58
|
+
if (!parsed)
|
|
59
|
+
return null;
|
|
60
|
+
const projected = await repos.listRepos(workspaceId);
|
|
61
|
+
const repo = projected.find((candidate) => candidate.owner.toLowerCase() === parsed.owner.toLowerCase() &&
|
|
62
|
+
candidate.name.toLowerCase() === parsed.repo.toLowerCase());
|
|
63
|
+
if (!repo)
|
|
64
|
+
return null;
|
|
65
|
+
// ONE batched read of the account services behind the board's frames, the sole repo ⇄ frame
|
|
66
|
+
// linkage (`Service.repoGithubId`), rather than a lookup per frame.
|
|
67
|
+
const services = await repos.listServicesForFrames(frames.map((frame) => frame.id));
|
|
68
|
+
const linked = services.filter((service) => service.repoGithubId === repo.githubId);
|
|
69
|
+
// Several means a monorepo whose subdirectories are separate services: which one an issue
|
|
70
|
+
// belongs to is not something the repository link can answer, so it stays a question.
|
|
71
|
+
if (linked.length !== 1)
|
|
72
|
+
return null;
|
|
73
|
+
return frames.find((frame) => frame.id === linked[0].frameBlockId) ?? null;
|
|
74
|
+
}
|
|
75
|
+
export function createTaskFromIssueAction(board, deps, repos) {
|
|
76
|
+
return {
|
|
77
|
+
actionId: 'create-task-from-issue',
|
|
78
|
+
purpose: 'File a board task from a tracker issue named by its URL (a GitHub or GitLab issue, a Jira ' +
|
|
79
|
+
'or Linear ticket), importing the issue and linking it to the task as agent context.',
|
|
80
|
+
examples: [
|
|
81
|
+
'create a task from https://github.com/acme/api/issues/12',
|
|
82
|
+
'file https://acme.atlassian.net/browse/PROJ-7 as a task on the billing service',
|
|
83
|
+
],
|
|
84
|
+
parameters: PARAMETERS,
|
|
85
|
+
async run({ workspaceId, arguments: args, editor, userId }) {
|
|
86
|
+
const url = readArgument(args, 'issueUrl');
|
|
87
|
+
if (url === undefined)
|
|
88
|
+
return needsInput('missing_argument', 'issueUrl');
|
|
89
|
+
const recognised = await deps.matchIssueSources(workspaceId, url);
|
|
90
|
+
if (recognised.length === 0)
|
|
91
|
+
return needsInput('unknown_issue_source', 'issueUrl');
|
|
92
|
+
// `source` is a TIE-BREAK among the trackers that recognised the reference, never a filter
|
|
93
|
+
// over them. WHICH trackers can read a reference is the providers' answer and nobody else's,
|
|
94
|
+
// so a stated name that is none of them says the NAME was wrong, not that the reference is
|
|
95
|
+
// unreadable: dropping to zero there would turn a pasted GitHub URL the model labelled
|
|
96
|
+
// `jira` into "no connected tracker recognises that link", which is both false and a remedy
|
|
97
|
+
// (go and connect one) that fixes nothing. Ignored, the answer is the one the providers gave.
|
|
98
|
+
const stated = readArgument(args, 'source')?.toLowerCase();
|
|
99
|
+
const chosen = stated === undefined ? recognised : recognised.filter((match) => match.source === stated);
|
|
100
|
+
const matches = chosen.length === 0 ? recognised : chosen;
|
|
101
|
+
if (matches.length > 1) {
|
|
102
|
+
// The question names the `source` FIELD, not the URL that was fine: a bare `PROJ-12` is
|
|
103
|
+
// legitimately both a Jira key and a Linear identifier, and what is missing is which
|
|
104
|
+
// tracker was meant. Naming `issueUrl` here would offer tracker ids as the answer to a
|
|
105
|
+
// question about a URL, and the next turn has no `issueUrl` reading that accepts one.
|
|
106
|
+
return needsInput('ambiguous_issue_source', 'source', matches.map((match) => match.source));
|
|
107
|
+
}
|
|
108
|
+
const { source } = matches[0];
|
|
109
|
+
// Imported BEFORE the container is resolved: the issue is what the task is made of, so an
|
|
110
|
+
// unreachable or unauthorised one must fail while the board is still untouched.
|
|
111
|
+
const issue = await deps.importIssue(workspaceId, source, url);
|
|
112
|
+
const frames = serviceFramesOf(await board.listBoardBlocks(workspaceId));
|
|
113
|
+
const named = readArgument(args, 'service');
|
|
114
|
+
let container = null;
|
|
115
|
+
if (named !== undefined) {
|
|
116
|
+
const resolved = resolveNamedService(matchServiceByName(frames, named), 'service');
|
|
117
|
+
if (resolved.kind !== 'one')
|
|
118
|
+
return resolved.outcome;
|
|
119
|
+
container = resolved.frame;
|
|
120
|
+
}
|
|
121
|
+
else if (repos) {
|
|
122
|
+
container = await serviceForIssueRepo(repos, workspaceId, frames, issue.externalId);
|
|
123
|
+
}
|
|
124
|
+
if (!container) {
|
|
125
|
+
return needsInput('unresolved_issue_service', 'service', serviceTitles(frames));
|
|
126
|
+
}
|
|
127
|
+
const { block } = await deps.createTaskFromIssue({
|
|
128
|
+
workspaceId,
|
|
129
|
+
containerId: container.id,
|
|
130
|
+
source,
|
|
131
|
+
externalId: issue.externalId,
|
|
132
|
+
editor,
|
|
133
|
+
createdBy: userId,
|
|
134
|
+
});
|
|
135
|
+
return performed({
|
|
136
|
+
actionId: 'create-task-from-issue',
|
|
137
|
+
task: { blockId: block.id, title: block.title },
|
|
138
|
+
service: { blockId: container.id, title: container.title },
|
|
139
|
+
issue: { source, externalId: issue.externalId, url: issue.url },
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=taskFromIssue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taskFromIssue.js","sourceRoot":"","sources":["../../../../src/modules/assistant/actions/taskFromIssue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { AssistantActionBrief } from '@cat-factory/agents';
|
|
2
|
+
import type { DescriptorField, DescriptorFieldValues } from '@cat-factory/contracts';
|
|
3
|
+
import type { Block } from '@cat-factory/kernel';
|
|
4
|
+
import type { AssistantActionDefinition } from './types.js';
|
|
5
|
+
/** The id a model answers with when no action in the catalog fits. */
|
|
6
|
+
export declare const NO_ACTION = "none";
|
|
7
|
+
/** What the model chose, once the reply has been read. */
|
|
8
|
+
export type AssistantSelection = {
|
|
9
|
+
kind: 'action';
|
|
10
|
+
actionId: string;
|
|
11
|
+
arguments: Record<string, string>;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'none';
|
|
14
|
+
}
|
|
15
|
+
/** The reply was not a usable selection at all (empty, not JSON, or the wrong shape). */
|
|
16
|
+
| {
|
|
17
|
+
kind: 'unreadable';
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Read a model reply (already extracted from its fences by kernel's `extractJson`) as a selection.
|
|
21
|
+
*
|
|
22
|
+
* Every value is coerced to a STRING and non-string values are DROPPED rather than stringified: a
|
|
23
|
+
* number or an object where an argument was expected means the model answered a different shape
|
|
24
|
+
* than the one it was given, and `"[object Object]"` reaching a name match would be a lookup for
|
|
25
|
+
* something nobody can type. A dropped argument reads downstream exactly like an omitted one,
|
|
26
|
+
* which is the honest outcome, because the platform then asks about it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readAssistantSelection(value: unknown): AssistantSelection;
|
|
29
|
+
/**
|
|
30
|
+
* The catalog as the model is shown it, derived from the same declarations the validator reads.
|
|
31
|
+
*
|
|
32
|
+
* `help` is preferred over `label` for the argument line because the label is a form caption ("Repository
|
|
33
|
+
* URL") while the help text is the sentence that says what a value looks like, which is what a router
|
|
34
|
+
* needs. A field with no help falls back to its label rather than rendering an empty description.
|
|
35
|
+
*/
|
|
36
|
+
export declare function assistantActionBriefs(actions: readonly AssistantActionDefinition[]): AssistantActionBrief[];
|
|
37
|
+
/**
|
|
38
|
+
* Drop argument keys the chosen action does not declare.
|
|
39
|
+
*
|
|
40
|
+
* The shared descriptor validator refuses an unknown key, and it is right to: a form that posts one
|
|
41
|
+
* is a bug. A ROUTER that emits one is not the same event (the model was told the key list and put
|
|
42
|
+
* something else beside it), and refusing the whole turn for it throws away an otherwise correct
|
|
43
|
+
* routing over a field nothing would have read. Dropped, the turn proceeds on the declared
|
|
44
|
+
* arguments and any missing required one is asked about, which is where such a reply belongs.
|
|
45
|
+
*/
|
|
46
|
+
export declare function keepDeclaredArguments(fields: readonly DescriptorField[], args: Record<string, string>): DescriptorFieldValues;
|
|
47
|
+
/** A repository a request named, plus the subtree the reference itself pointed into. */
|
|
48
|
+
export interface AssistantRepoRef {
|
|
49
|
+
owner: string;
|
|
50
|
+
repo: string;
|
|
51
|
+
/**
|
|
52
|
+
* The repo-root-relative subdirectory the reference named, when it named one.
|
|
53
|
+
*
|
|
54
|
+
* Only a URL that points INTO the tree (`/tree/<ref>/packages/api`) carries this, and it is
|
|
55
|
+
* already checked against the shared path rule here, because a value recovered from a link has
|
|
56
|
+
* been through no validator on the way in.
|
|
57
|
+
*/
|
|
58
|
+
directory?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The repository a person named: a pasted web URL, or the bare `owner/repo` slug.
|
|
62
|
+
*
|
|
63
|
+
* The URL is the primary form and is parsed by the SHARED parser, the same one the repository
|
|
64
|
+
* picker resolves a paste with, so GitHub and GitLab shapes (subgroups, `/-/`, `/tree/<ref>/…`)
|
|
65
|
+
* and self-managed hosts are understood identically on both surfaces.
|
|
66
|
+
*
|
|
67
|
+
* The SLUG is accepted here and deliberately NOT in that shared parser, which declines it on
|
|
68
|
+
* purpose (a slug has no host, so the picker cannot tell one from a search term). This surface
|
|
69
|
+
* has the opposite problem: when a URL names a repository the workspace does not project, the
|
|
70
|
+
* turn answers with the projected repositories of the same NAME under other owners, and those
|
|
71
|
+
* near-misses are slugs. Offering `acme/payments` as the answer to "which repository did you
|
|
72
|
+
* mean?" and then refusing it as unreadable would be a question with no acceptable answer.
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseRepoRef(input: string): AssistantRepoRef | null;
|
|
75
|
+
/** A service frame a name matched, or why it did not match exactly one. */
|
|
76
|
+
export type ServiceMatch = {
|
|
77
|
+
kind: 'one';
|
|
78
|
+
frame: Block;
|
|
79
|
+
} | {
|
|
80
|
+
kind: 'none';
|
|
81
|
+
} | {
|
|
82
|
+
kind: 'many';
|
|
83
|
+
candidates: string[];
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Match a name the model copied out of a sentence against the board's service frames.
|
|
87
|
+
*
|
|
88
|
+
* Three passes, narrowest first, and the ORDER is the rule: an exact title always wins, a
|
|
89
|
+
* punctuation-insensitive match ("user service" ⇄ `user-service`) comes next, and only then a
|
|
90
|
+
* containment match, which is where "the API" finds `Payments API`. Stopping at the first pass
|
|
91
|
+
* that matches anything is what keeps a board holding both `Api` and `Api Gateway` from reporting
|
|
92
|
+
* them as ambiguous when one of them was named outright.
|
|
93
|
+
*
|
|
94
|
+
* A pass that matches SEVERAL is reported as ambiguous with their titles rather than resolved by a
|
|
95
|
+
* tiebreak, because every available tiebreak (shortest, first, most recently created) picks a
|
|
96
|
+
* service on grounds the person never stated. Ambiguity is a question, not a coin toss.
|
|
97
|
+
*
|
|
98
|
+
* BOTH sides of the two folded passes are checked for an empty fold, and each of the two is a
|
|
99
|
+
* separate bug the guard closes. An empty fold on the QUERY side would match everything; an empty
|
|
100
|
+
* fold on the FRAME side would make that one frame match everything, which is worse, because it
|
|
101
|
+
* turns every other name on the board into a false `ambiguous_service` and the frame itself is
|
|
102
|
+
* still unreachable except by its exact title. A title with nothing to fold is matched by pass 1
|
|
103
|
+
* alone, which compares the real characters and needs no fold at all.
|
|
104
|
+
*/
|
|
105
|
+
export declare function matchServiceByName(frames: readonly Block[], name: string): ServiceMatch;
|
|
106
|
+
/** The service frames of a board: the blocks a connection or a task placement may name. */
|
|
107
|
+
export declare function serviceFramesOf(blocks: readonly Block[]): Block[];
|
|
108
|
+
/**
|
|
109
|
+
* The repository an issue's external id names, or null when the id does not name one.
|
|
110
|
+
*
|
|
111
|
+
* Every REPO-BACKED task source shapes its canonical id as `<project path>#<number>` (GitHub's
|
|
112
|
+
* `acme/payments#12`, GitLab's `group/subgroup/billing#5`), so the project path is everything
|
|
113
|
+
* before the last `#`, its last segment is the repository and the rest is the owner (a GitLab
|
|
114
|
+
* namespace legitimately has several segments; a GitHub owner has one).
|
|
115
|
+
*
|
|
116
|
+
* Null for an id with no path at all, which is exactly what a Jira or Linear key (`PROJ-12`) is.
|
|
117
|
+
* That is not a failure to parse: those trackers have no repository, so there is nothing here to
|
|
118
|
+
* infer a service from and the turn asks which service to file under. A deployment-registered
|
|
119
|
+
* source that shapes its ids differently lands in the same place, which is the safe direction:
|
|
120
|
+
* a question, never a task filed against a repository nobody named.
|
|
121
|
+
*/
|
|
122
|
+
export declare function issueRepoSlug(externalId: string): {
|
|
123
|
+
owner: string;
|
|
124
|
+
repo: string;
|
|
125
|
+
} | null;
|
|
126
|
+
//# sourceMappingURL=assistant.logic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/assistant/assistant.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAEpF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAY3D,sEAAsE;AACtE,eAAO,MAAM,SAAS,SAAS,CAAA;AAE/B,0DAA0D;AAC1D,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,yFAAyF;GACvF;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAA;AAE1B;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAiBzE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,yBAAyB,EAAE,GAC5C,oBAAoB,EAAE,CAWxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,eAAe,EAAE,EAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,qBAAqB,CAOvB;AAED,wFAAwF;AACxF,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAWnE;AAsBD,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAsB1C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAyBvF;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,EAAE,CAKjE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAMxF"}
|