@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.
Files changed (73) hide show
  1. package/dist/container/assistant-module.d.ts +48 -0
  2. package/dist/container/assistant-module.d.ts.map +1 -0
  3. package/dist/container/assistant-module.js +87 -0
  4. package/dist/container/assistant-module.js.map +1 -0
  5. package/dist/container/engine-collaborators.d.ts +3 -0
  6. package/dist/container/engine-collaborators.d.ts.map +1 -1
  7. package/dist/container/engine-collaborators.js +6 -1
  8. package/dist/container/engine-collaborators.js.map +1 -1
  9. package/dist/container.d.ts +8 -0
  10. package/dist/container.d.ts.map +1 -1
  11. package/dist/container.js +1 -0
  12. package/dist/container.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/modules/assistant/AssistantService.d.ts +85 -0
  17. package/dist/modules/assistant/AssistantService.d.ts.map +1 -0
  18. package/dist/modules/assistant/AssistantService.js +254 -0
  19. package/dist/modules/assistant/AssistantService.js.map +1 -0
  20. package/dist/modules/assistant/actions/deps.d.ts +71 -0
  21. package/dist/modules/assistant/actions/deps.d.ts.map +1 -0
  22. package/dist/modules/assistant/actions/deps.js +2 -0
  23. package/dist/modules/assistant/actions/deps.js.map +1 -0
  24. package/dist/modules/assistant/actions/index.d.ts +20 -0
  25. package/dist/modules/assistant/actions/index.d.ts.map +1 -0
  26. package/dist/modules/assistant/actions/index.js +28 -0
  27. package/dist/modules/assistant/actions/index.js.map +1 -0
  28. package/dist/modules/assistant/actions/serviceDependency.d.ts +4 -0
  29. package/dist/modules/assistant/actions/serviceDependency.d.ts.map +1 -0
  30. package/dist/modules/assistant/actions/serviceDependency.js +99 -0
  31. package/dist/modules/assistant/actions/serviceDependency.js.map +1 -0
  32. package/dist/modules/assistant/actions/serviceFromRepo.d.ts +4 -0
  33. package/dist/modules/assistant/actions/serviceFromRepo.d.ts.map +1 -0
  34. package/dist/modules/assistant/actions/serviceFromRepo.js +101 -0
  35. package/dist/modules/assistant/actions/serviceFromRepo.js.map +1 -0
  36. package/dist/modules/assistant/actions/shared.d.ts +31 -0
  37. package/dist/modules/assistant/actions/shared.d.ts.map +1 -0
  38. package/dist/modules/assistant/actions/shared.js +37 -0
  39. package/dist/modules/assistant/actions/shared.js.map +1 -0
  40. package/dist/modules/assistant/actions/taskFromIssue.d.ts +4 -0
  41. package/dist/modules/assistant/actions/taskFromIssue.d.ts.map +1 -0
  42. package/dist/modules/assistant/actions/taskFromIssue.js +144 -0
  43. package/dist/modules/assistant/actions/taskFromIssue.js.map +1 -0
  44. package/dist/modules/assistant/assistant.logic.d.ts +126 -0
  45. package/dist/modules/assistant/assistant.logic.d.ts.map +1 -0
  46. package/dist/modules/assistant/assistant.logic.js +215 -0
  47. package/dist/modules/assistant/assistant.logic.js.map +1 -0
  48. package/dist/modules/assistant/types.d.ts +45 -0
  49. package/dist/modules/assistant/types.d.ts.map +1 -0
  50. package/dist/modules/assistant/types.js +9 -0
  51. package/dist/modules/assistant/types.js.map +1 -0
  52. package/dist/modules/board/BoardService.d.ts +9 -3
  53. package/dist/modules/board/BoardService.d.ts.map +1 -1
  54. package/dist/modules/board/BoardService.js +8 -2
  55. package/dist/modules/board/BoardService.js.map +1 -1
  56. package/dist/modules/board/serviceRepoLinkage.d.ts +16 -1
  57. package/dist/modules/board/serviceRepoLinkage.d.ts.map +1 -1
  58. package/dist/modules/board/serviceRepoLinkage.js.map +1 -1
  59. package/dist/modules/execution/HumanTestController.d.ts.map +1 -1
  60. package/dist/modules/execution/HumanTestController.js +3 -6
  61. package/dist/modules/execution/HumanTestController.js.map +1 -1
  62. package/dist/modules/execution/RunStateMachine.d.ts +13 -4
  63. package/dist/modules/execution/RunStateMachine.d.ts.map +1 -1
  64. package/dist/modules/execution/RunStateMachine.js +18 -14
  65. package/dist/modules/execution/RunStateMachine.js.map +1 -1
  66. package/dist/modules/execution/VisualConfirmationController.d.ts.map +1 -1
  67. package/dist/modules/execution/VisualConfirmationController.js +3 -6
  68. package/dist/modules/execution/VisualConfirmationController.js.map +1 -1
  69. package/dist/modules/notifications/NotificationService.d.ts +46 -7
  70. package/dist/modules/notifications/NotificationService.d.ts.map +1 -1
  71. package/dist/modules/notifications/NotificationService.js +60 -18
  72. package/dist/modules/notifications/NotificationService.js.map +1 -1
  73. 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"}