@dahrk/linear 0.1.0 → 0.2.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 (67) hide show
  1. package/README.md +24 -12
  2. package/dist/batch-source.d.ts +63 -0
  3. package/dist/batch-source.d.ts.map +1 -0
  4. package/dist/batch-source.js +149 -0
  5. package/dist/batch-source.js.map +1 -0
  6. package/dist/comments.d.ts +36 -0
  7. package/dist/comments.d.ts.map +1 -0
  8. package/dist/comments.js +104 -0
  9. package/dist/comments.js.map +1 -0
  10. package/dist/documents.d.ts +1 -15
  11. package/dist/documents.d.ts.map +1 -1
  12. package/dist/documents.js +37 -27
  13. package/dist/documents.js.map +1 -1
  14. package/dist/format-action.d.ts +25 -0
  15. package/dist/format-action.d.ts.map +1 -0
  16. package/dist/format-action.js +250 -0
  17. package/dist/format-action.js.map +1 -0
  18. package/dist/index.d.ts +119 -15
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +231 -59
  21. package/dist/index.js.map +1 -1
  22. package/dist/issue-graph.d.ts +37 -0
  23. package/dist/issue-graph.d.ts.map +1 -0
  24. package/dist/issue-graph.js +125 -0
  25. package/dist/issue-graph.js.map +1 -0
  26. package/dist/issues.d.ts +27 -2
  27. package/dist/issues.d.ts.map +1 -1
  28. package/dist/issues.js +33 -10
  29. package/dist/issues.js.map +1 -1
  30. package/dist/labels.d.ts +48 -1
  31. package/dist/labels.d.ts.map +1 -1
  32. package/dist/labels.js +72 -24
  33. package/dist/labels.js.map +1 -1
  34. package/dist/linear-client.d.ts +51 -0
  35. package/dist/linear-client.d.ts.map +1 -1
  36. package/dist/linear-client.js +233 -34
  37. package/dist/linear-client.js.map +1 -1
  38. package/dist/oauth.d.ts +52 -12
  39. package/dist/oauth.d.ts.map +1 -1
  40. package/dist/oauth.js +91 -34
  41. package/dist/oauth.js.map +1 -1
  42. package/dist/recording-client.d.ts +20 -2
  43. package/dist/recording-client.d.ts.map +1 -1
  44. package/dist/recording-client.js +39 -1
  45. package/dist/recording-client.js.map +1 -1
  46. package/dist/responding-client.d.ts +49 -0
  47. package/dist/responding-client.d.ts.map +1 -0
  48. package/dist/responding-client.js +47 -0
  49. package/dist/responding-client.js.map +1 -0
  50. package/dist/teams.d.ts +20 -0
  51. package/dist/teams.d.ts.map +1 -0
  52. package/dist/teams.js +32 -0
  53. package/dist/teams.js.map +1 -0
  54. package/package.json +8 -10
  55. package/src/batch-source.ts +208 -0
  56. package/src/comments.ts +126 -0
  57. package/src/documents.ts +162 -0
  58. package/src/format-action.ts +279 -0
  59. package/src/index.ts +617 -0
  60. package/src/issue-graph.ts +169 -0
  61. package/src/issues.ts +142 -0
  62. package/src/labels.ts +254 -0
  63. package/src/linear-client.ts +448 -0
  64. package/src/oauth.ts +255 -0
  65. package/src/recording-client.ts +141 -0
  66. package/src/responding-client.ts +106 -0
  67. package/src/teams.ts +44 -0
package/README.md CHANGED
@@ -1,19 +1,31 @@
1
1
  # @dahrk/linear
2
2
 
3
- **Confidentiality:** Internal
4
- **Status:** DRAFT - UNREVIEWED
3
+ The Linear-native control surface used by [Dahrk](https://dahrk.ai), a Linear-native agent-workflow
4
+ harness. Linear is the substrate and the only source; this package is how Dahrk speaks to it.
5
5
 
6
- The Linear-native control surface and webhook verification. Linear is the substrate and the only
7
- source; this package is how Dahrk speaks to it.
6
+ - **Intake-side:** verify webhook signatures and normalise them to an internal `LinearEvent`.
7
+ - **Session-side:** the Agent Session API - activities, the agent-plan checklist, `elicitation` gates,
8
+ the `prompted` drive, the `stop` signal, and `externalUrls`.
8
9
 
9
- - **Intake-side:** verify webhook signatures; normalise to the internal `LinearEvent`.
10
- - **Session-side:** the Agent Session API - activities, the agent-plan checklist, `elicitation`
11
- gates, the `prompted` drive, the `stop` signal, `externalUrls`.
10
+ Used by both the hub (intake plus session) and the edge node (session, while a stage runs). Implemented
11
+ against the Linear API directly, on top of the Linear TypeScript SDK.
12
12
 
13
- Used by both `hub` (intake + session) and `edge` (session, while a stage runs). Re-implemented
14
- against the API directly; cyrus is a reference. Intended runtime dependency: the Linear TypeScript
15
- SDK. See `../../docs/01-build-spec.md` section 14.
13
+ ## Stability
16
14
 
17
- ## Sources
15
+ This package is pre-1.0, so **a minor bump can break**. Note that a caret range on a `0.x` version does
16
+ not cross the minor: `^0.1.0` will not resolve `0.2.0`. Pin deliberately.
18
17
 
19
- - `../../docs/01-build-spec.md`, the design's doc 05 (Linear feature coverage).
18
+ ## Notes for the curious
19
+
20
+ **There is deliberately no `repository` field**, which is why npm shows no repository row. This package
21
+ is published from a private repo, so the accurate link would 404 for everyone who can read the package,
22
+ and pointing it at the public `dahrk-node` would send readers to a repo the source is not in. The links
23
+ below are the real front doors.
24
+
25
+ ## Links
26
+
27
+ - Documentation: [dahrk.ai/docs](https://dahrk.ai/docs)
28
+ - Issues: [github.com/dahrkai/dahrk-node/issues](https://github.com/dahrkai/dahrk-node/issues)
29
+ - Edge client: [github.com/dahrkai/dahrk-node](https://github.com/dahrkai/dahrk-node)
30
+
31
+ Apache-2.0, © Skakel Labs.
@@ -0,0 +1,63 @@
1
+ import type { BatchMember, BatchBlockEdge } from "@dahrk/contracts";
2
+ /** An issue that `blocks` a child, as read from the child's `inverseRelations`. `stateType` decides
3
+ * whether it has settled (terminal); `stateName` is carried for the decline note only, never matched. */
4
+ export interface RawBlocker {
5
+ id?: string;
6
+ identifier?: string;
7
+ stateType?: string;
8
+ stateName?: string;
9
+ }
10
+ /** A child issue as returned by Linear before normalisation, with its state and blockers resolved. */
11
+ export interface RawChild {
12
+ id?: string;
13
+ identifier?: string;
14
+ /** The child's workflow-state category (`state.type`); terminal children are dropped. */
15
+ stateType?: string;
16
+ /** Linear priority (`0` = none, `1` = urgent … `4` = low). */
17
+ priority?: number;
18
+ /** The child's rank within the parent (`subIssueSortOrder`, falling back to `sortOrder`). */
19
+ containerRank?: number;
20
+ /** The issues that `blocks` this child (its `inverseRelations` of type `blocks`). */
21
+ blockers: RawBlocker[];
22
+ }
23
+ /** The read seam the assembly depends on; the live impl wraps the SDK, tests inject a fake. */
24
+ export interface ParentBatchSource {
25
+ /** The parent's direct children, each with its state and its `blocks` blockers, in any order. */
26
+ children(parentIssueId: string): Promise<RawChild[]>;
27
+ }
28
+ /** One unsettled blocker outside the selected source that prevents Batch creation. Shaped like the
29
+ * hub's `OpenBlocker` so the decline note names it (`identifier` is the key; `stateName` is display). */
30
+ export interface ExternalBlocker {
31
+ identifier: string;
32
+ stateName: string;
33
+ }
34
+ /** The normalised parent-Batch snapshot: the ordered members, the internal `blocks` edges between them,
35
+ * and any external blocker that must block creation. */
36
+ export interface ParentBatchSnapshot {
37
+ members: BatchMember[];
38
+ blocks: BatchBlockEdge[];
39
+ externalBlockers: ExternalBlocker[];
40
+ }
41
+ /** How many children ride one snapshot. A parent with more direct children than this is unusual; the
42
+ * cap keeps the intake read bounded, mirroring the neighbours read in `issue-graph.ts`. */
43
+ export declare const MAX_BATCH_CHILDREN = 200;
44
+ /**
45
+ * Collect the parent's direct nonterminal children as a {@link ParentBatchSnapshot} from a source: drop
46
+ * children with no id/identifier; drop terminal (`completed`/`canceled`) children; classify each
47
+ * member's `blocks` blockers into internal edges (blocker is another member) or external blockers
48
+ * (blocker is outside the set and unsettled); order members by priority, then container rank, then
49
+ * identifier. Pure of any network.
50
+ */
51
+ export declare function collectParentBatchSource(source: ParentBatchSource, parentIssueId: string): Promise<ParentBatchSnapshot>;
52
+ /** The live `ParentBatchSource`, backed by a Linear bearer token. Uses the typed SDK: the parent's
53
+ * `children`, each child's `state`/`priority`/`subIssueSortOrder`/`sortOrder`, and each child's
54
+ * `inverseRelations` of type `blocks` resolved to `{id, identifier, state}`. Capped like the
55
+ * neighbours read. */
56
+ export declare function linearParentBatchSource(token: string): ParentBatchSource;
57
+ /**
58
+ * Take the parent-Batch snapshot for `parentIssueId` as a {@link ParentBatchSnapshot}, ready for the
59
+ * hub to persist. The single entry point the hub calls (mirrors `fetchRelatedIssues`). A hard failure
60
+ * (auth, network) propagates so the caller can log and proceed.
61
+ */
62
+ export declare function fetchParentBatchSource(token: string, parentIssueId: string): Promise<ParentBatchSnapshot>;
63
+ //# sourceMappingURL=batch-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-source.d.ts","sourceRoot":"","sources":["../src/batch-source.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGpE;0GAC0G;AAC1G,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sGAAsG;AACtG,MAAM,WAAW,QAAQ;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,+FAA+F;AAC/F,MAAM,WAAW,iBAAiB;IAChC,iGAAiG;IACjG,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CACtD;AAED;0GAC0G;AAC1G,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;yDACyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACrC;AAED;4FAC4F;AAC5F,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAQtC;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,iBAAiB,EACzB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,CAAC,CAyD9B;AAED;;;uBAGuB;AACvB,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CA2CxE;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,CAAC,CAE9B"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Snapshot a parent control issue as a deterministic child-sourced Batch (DHK-1154; ADRs 0016-0017).
3
+ *
4
+ * Assigning or @mentioning Dahrk on a PARENT issue launches its direct child work as one immutable
5
+ * Batch. This module takes ONE credential-resolved read of the parent's direct, nonterminal children
6
+ * (the control issue itself is never included - children only) and normalises it into a
7
+ * {@link ParentBatchSnapshot}: an ordered list of {@link BatchMember}s keyed by stable issue UUID, the
8
+ * explicit `blocks` edges BETWEEN members ({@link BatchBlockEdge}), and any unsettled blocker OUTSIDE
9
+ * the member set that must block creation (`externalBlockers`).
10
+ *
11
+ * Two rules pin the dependency model:
12
+ * - Containment is not a dependency. A parent/child relation NEVER produces an edge; only an explicit
13
+ * Linear `blocks` relation does. Children carry only their own `blocks` blockers, so containment is
14
+ * structurally incapable of creating one here.
15
+ * - "External" is relative to the SELECTED source. A blocker that is itself another selected child is
16
+ * an internal edge, not an external blocker, even when unsettled - that is a dependency to sequence,
17
+ * not a refusal. The membership test runs before the external-blocker test.
18
+ *
19
+ * The `ParentBatchSource` seam keeps the assembly pure and unit-testable (the `collectRelatedIssues`
20
+ * pattern from `issue-graph.ts`): the live impl wraps the SDK, tests inject a fake.
21
+ */
22
+ import { LinearClient } from "@linear/sdk";
23
+ import { isBlockerSettled } from "./labels.js";
24
+ /** How many children ride one snapshot. A parent with more direct children than this is unusual; the
25
+ * cap keeps the intake read bounded, mirroring the neighbours read in `issue-graph.ts`. */
26
+ export const MAX_BATCH_CHILDREN = 200;
27
+ /** Sort key for priority: urgent (1) first, none (0) last. Linear priority is `0=none,1=urgent..4=low`,
28
+ * so "order by priority" must map `0` to the end rather than the front. Pure, pinned by a test. */
29
+ function priorityRank(priority) {
30
+ return priority === 0 ? Number.POSITIVE_INFINITY : priority;
31
+ }
32
+ /**
33
+ * Collect the parent's direct nonterminal children as a {@link ParentBatchSnapshot} from a source: drop
34
+ * children with no id/identifier; drop terminal (`completed`/`canceled`) children; classify each
35
+ * member's `blocks` blockers into internal edges (blocker is another member) or external blockers
36
+ * (blocker is outside the set and unsettled); order members by priority, then container rank, then
37
+ * identifier. Pure of any network.
38
+ */
39
+ export async function collectParentBatchSource(source, parentIssueId) {
40
+ const raw = await source.children(parentIssueId);
41
+ // Keep only real, nonterminal children. A child needs a stable id AND a human identifier; a terminal
42
+ // child (settled state) is finished work, not something to launch.
43
+ const kept = [];
44
+ for (const c of raw) {
45
+ const id = c.id?.trim();
46
+ const identifier = c.identifier?.trim();
47
+ if (!id || !identifier)
48
+ continue;
49
+ if (isBlockerSettled(c.stateType))
50
+ continue;
51
+ kept.push(c);
52
+ }
53
+ const memberIds = new Set(kept.map((c) => c.id.trim()));
54
+ const members = kept.map((c) => ({
55
+ issueId: c.id.trim(),
56
+ issueIdentifier: c.identifier.trim(),
57
+ stateType: (c.stateType ?? "").trim() || "unknown",
58
+ priority: c.priority ?? 0,
59
+ containerRank: c.containerRank ?? 0,
60
+ }));
61
+ // Deterministic order: urgent priority first (none last), then container rank ascending, then the
62
+ // human identifier. Every downstream reader sees the same sequence, so admission is replayable.
63
+ members.sort((a, b) => priorityRank(a.priority) - priorityRank(b.priority) ||
64
+ a.containerRank - b.containerRank ||
65
+ a.issueIdentifier.localeCompare(b.issueIdentifier));
66
+ // Classify blockers in member order so edges and external blockers are deterministic. A blocker that
67
+ // is itself a selected member is an internal edge (a dependency to sequence); one outside the set that
68
+ // has not settled is an external blocker (a refusal); a settled external blocker is simply done.
69
+ const byId = new Map(kept.map((c) => [c.id.trim(), c]));
70
+ const blocks = [];
71
+ const externalBlockers = [];
72
+ const seenExternal = new Set();
73
+ for (const m of members) {
74
+ const c = byId.get(m.issueId);
75
+ for (const b of c.blockers) {
76
+ const blockerId = b.id?.trim();
77
+ if (blockerId && memberIds.has(blockerId)) {
78
+ blocks.push({ blockerIssueId: blockerId, blockedIssueId: m.issueId });
79
+ continue;
80
+ }
81
+ if (isBlockerSettled(b.stateType))
82
+ continue;
83
+ const identifier = (b.identifier ?? "").trim() || "unknown";
84
+ if (seenExternal.has(identifier))
85
+ continue;
86
+ seenExternal.add(identifier);
87
+ externalBlockers.push({ identifier, stateName: (b.stateName ?? "").trim() || "unknown" });
88
+ }
89
+ }
90
+ return { members, blocks, externalBlockers };
91
+ }
92
+ /** The live `ParentBatchSource`, backed by a Linear bearer token. Uses the typed SDK: the parent's
93
+ * `children`, each child's `state`/`priority`/`subIssueSortOrder`/`sortOrder`, and each child's
94
+ * `inverseRelations` of type `blocks` resolved to `{id, identifier, state}`. Capped like the
95
+ * neighbours read. */
96
+ export function linearParentBatchSource(token) {
97
+ const client = new LinearClient({ accessToken: token });
98
+ return {
99
+ async children(parentIssueId) {
100
+ const parent = await client.issue(parentIssueId);
101
+ const conn = await parent.children({ first: MAX_BATCH_CHILDREN });
102
+ const children = [];
103
+ for (const c of conn.nodes) {
104
+ const [state, inverse] = await Promise.all([
105
+ c.state,
106
+ // A `blocks` relation pointing AT the child means that issue is the child's blocker (the same
107
+ // direction `fetchOpenBlockers` reads). `relations` would return what the child blocks - the
108
+ // opposite question.
109
+ c.inverseRelations({ first: 25 }),
110
+ ]);
111
+ const blockers = [];
112
+ for (const rel of inverse.nodes) {
113
+ if (rel.type !== "blocks")
114
+ continue;
115
+ const other = await rel.issue;
116
+ if (!other)
117
+ continue;
118
+ const bState = await other.state;
119
+ blockers.push({
120
+ ...(other.id ? { id: other.id } : {}),
121
+ ...(other.identifier ? { identifier: other.identifier } : {}),
122
+ ...(bState?.type ? { stateType: bState.type } : {}),
123
+ ...(bState?.name ? { stateName: bState.name } : {}),
124
+ });
125
+ }
126
+ // `subIssueSortOrder` is the rank within the parent; fall back to `sortOrder` when absent.
127
+ const containerRank = c.subIssueSortOrder ?? c.sortOrder ?? 0;
128
+ children.push({
129
+ ...(c.id ? { id: c.id } : {}),
130
+ ...(c.identifier ? { identifier: c.identifier } : {}),
131
+ ...(state?.type ? { stateType: state.type } : {}),
132
+ ...(typeof c.priority === "number" ? { priority: c.priority } : {}),
133
+ containerRank,
134
+ blockers,
135
+ });
136
+ }
137
+ return children;
138
+ },
139
+ };
140
+ }
141
+ /**
142
+ * Take the parent-Batch snapshot for `parentIssueId` as a {@link ParentBatchSnapshot}, ready for the
143
+ * hub to persist. The single entry point the hub calls (mirrors `fetchRelatedIssues`). A hard failure
144
+ * (auth, network) propagates so the caller can log and proceed.
145
+ */
146
+ export async function fetchParentBatchSource(token, parentIssueId) {
147
+ return collectParentBatchSource(linearParentBatchSource(token), parentIssueId);
148
+ }
149
+ //# sourceMappingURL=batch-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-source.js","sourceRoot":"","sources":["../src/batch-source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { IssueComment } from "@dahrk/contracts";
2
+ /** A comment as returned by Linear before normalisation. */
3
+ export interface RawComment {
4
+ id: string;
5
+ body?: string;
6
+ createdAt?: Date | string;
7
+ /** Author user id, compared against the app user id to drop the harness's own posts. */
8
+ authorId?: string;
9
+ /** Author display name, surfaced to the agent. */
10
+ authorName?: string;
11
+ }
12
+ /** The read seam the assembly depends on; the live impl wraps GraphQL, tests inject a fake. */
13
+ export interface CommentSource {
14
+ /** The issue's comments, in whatever order Linear returns them. */
15
+ issueComments(issueId: string): Promise<RawComment[]>;
16
+ /** The id of the app user this token authenticates as, or null when it cannot be resolved. */
17
+ appUserId(): Promise<string | null>;
18
+ }
19
+ /**
20
+ * Collect an issue's comments from a source: drop the app user's own posts, drop empty bodies, and
21
+ * order oldest first so the thread reads as a conversation. Pure of any network - the source does the
22
+ * I/O.
23
+ *
24
+ * Comments with no resolvable timestamp sort last rather than being dropped: an unorderable comment is
25
+ * still content worth showing, and losing it silently would be worse than showing it out of order.
26
+ */
27
+ export declare function collectIssueComments(source: CommentSource, issueId: string): Promise<IssueComment[]>;
28
+ /** The live `CommentSource`, backed by a Linear bearer token. Uses the typed SDK. */
29
+ export declare function linearCommentSource(token: string): CommentSource;
30
+ /**
31
+ * Fetch an issue's comment thread as contract `IssueComment`s, ready to snapshot into a run. The
32
+ * single entry point the hub calls. A hard failure (auth, network) propagates so the caller can log
33
+ * and proceed with none.
34
+ */
35
+ export declare function fetchIssueComments(token: string, issueId: string): Promise<IssueComment[]>;
36
+ //# sourceMappingURL=comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,+FAA+F;AAC/F,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACtD,8FAA8F;IAC9F,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACrC;AAYD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,EAAE,CAAC,CAmBzB;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAqChE;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAEhG"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Fetch the comment thread on a Linear issue, so the hub can snapshot it into a run and surface it to
3
+ * the agent. Until now nothing in the harness read comment bodies at all: an agent saw only whatever
4
+ * Linear happened to inline in the webhook's `promptContext`, so the conversation that actually
5
+ * decided the shape of a ticket was invisible to the stage doing the work.
6
+ *
7
+ * The one piece of logic that is not a plain read: the app user's OWN comments are dropped. The
8
+ * harness posts its stage summaries back through `commentOnIssue`, so without this filter every stage
9
+ * would read the previous stage's summary as though a human had written it - the run talking to itself,
10
+ * with the noise compounding on each continuation.
11
+ *
12
+ * The `CommentSource` seam keeps the assembly pure and unit-testable: the live source
13
+ * (`linearCommentSource`) speaks GraphQL, while tests inject a fake.
14
+ */
15
+ import { LinearClient } from "@linear/sdk";
16
+ /** Normalise a timestamp Linear may hand back as a Date or a string into ISO 8601. */
17
+ function isoOf(value) {
18
+ if (value instanceof Date)
19
+ return value.toISOString();
20
+ if (typeof value === "string" && value.trim()) {
21
+ const parsed = new Date(value);
22
+ return Number.isNaN(parsed.getTime()) ? "" : parsed.toISOString();
23
+ }
24
+ return "";
25
+ }
26
+ /**
27
+ * Collect an issue's comments from a source: drop the app user's own posts, drop empty bodies, and
28
+ * order oldest first so the thread reads as a conversation. Pure of any network - the source does the
29
+ * I/O.
30
+ *
31
+ * Comments with no resolvable timestamp sort last rather than being dropped: an unorderable comment is
32
+ * still content worth showing, and losing it silently would be worse than showing it out of order.
33
+ */
34
+ export async function collectIssueComments(source, issueId) {
35
+ const [raw, appUserId] = await Promise.all([source.issueComments(issueId), source.appUserId()]);
36
+ const out = [];
37
+ for (const comment of raw) {
38
+ if (appUserId && comment.authorId === appUserId)
39
+ continue;
40
+ const body = (comment.body ?? "").trim();
41
+ if (!body)
42
+ continue;
43
+ out.push({
44
+ id: comment.id,
45
+ author: (comment.authorName ?? "").trim(),
46
+ createdAt: isoOf(comment.createdAt),
47
+ body,
48
+ });
49
+ }
50
+ return out.sort((a, b) => {
51
+ if (!a.createdAt)
52
+ return 1;
53
+ if (!b.createdAt)
54
+ return -1;
55
+ return a.createdAt.localeCompare(b.createdAt);
56
+ });
57
+ }
58
+ /** The live `CommentSource`, backed by a Linear bearer token. Uses the typed SDK. */
59
+ export function linearCommentSource(token) {
60
+ const client = new LinearClient({ accessToken: token });
61
+ return {
62
+ async issueComments(issueId) {
63
+ // Bounded, unpaginated cap, matching `linearDocumentSource.issueDocuments`: a thread longer than
64
+ // 100 comments silently drops the overflow. Deliberate, not a bug - the prompt inlines only the
65
+ // most recent few thousand characters anyway. Revisit with cursor pagination if it bites.
66
+ const issue = await client.issue(issueId);
67
+ const conn = await issue.comments({ first: 100 });
68
+ const nodes = await Promise.all(conn.nodes.map(async (c) => {
69
+ // `user` is a lazy reference on the SDK's Comment; resolving it per comment is what lets us
70
+ // both filter the app user's posts and name the human author. A comment whose author does not
71
+ // resolve (a deleted user, an integration post) still rides through, unattributed.
72
+ const user = await c.user;
73
+ return {
74
+ id: c.id,
75
+ body: c.body,
76
+ createdAt: c.createdAt,
77
+ ...(user?.id ? { authorId: user.id } : {}),
78
+ ...(user?.displayName || user?.name ? { authorName: user.displayName ?? user.name } : {}),
79
+ };
80
+ }));
81
+ return nodes;
82
+ },
83
+ async appUserId() {
84
+ try {
85
+ const viewer = await client.viewer;
86
+ return viewer?.id ?? null;
87
+ }
88
+ catch {
89
+ // Failing to resolve the app user must not lose the thread. We return null and accept that the
90
+ // harness's own comments ride through this once, rather than dropping every comment.
91
+ return null;
92
+ }
93
+ },
94
+ };
95
+ }
96
+ /**
97
+ * Fetch an issue's comment thread as contract `IssueComment`s, ready to snapshot into a run. The
98
+ * single entry point the hub calls. A hard failure (auth, network) propagates so the caller can log
99
+ * and proceed with none.
100
+ */
101
+ export async function fetchIssueComments(token, issueId) {
102
+ return collectIssueComments(linearCommentSource(token), issueId);
103
+ }
104
+ //# sourceMappingURL=comments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.js","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"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"}
@@ -1,17 +1,3 @@
1
- /**
2
- * Fetch the Linear Documents attached to an issue, so the hub can snapshot their content into a run
3
- * and surface it to the agent (the harness was webhook-payload-only before, so a document reached the
4
- * agent only as a URL, never as content). This is a read against the same per-connection Linear token
5
- * the hub already uses to post back; fetching content is data assembly, not control flow.
6
- *
7
- * Two sources, de-duped by document id:
8
- * 1. Documents directly associated with the issue (`issue.documents`).
9
- * 2. Documents linked via the issue's attachments whose `url` is a Linear document URL
10
- * (`https://linear.app/<ws>/document/<...>-<slug>`), resolved through `document(id:)`.
11
- *
12
- * The `DocumentSource` seam keeps the assembly pure and unit-testable: the live source
13
- * (`linearDocumentSource`) speaks GraphQL, while tests inject a fake.
14
- */
15
1
  import type { AttachedDocument } from "@dahrk/contracts";
16
2
  /** A document as returned by Linear before slug normalisation (slugId is Linear's URL-safe slug). */
17
3
  export interface RawDocument {
@@ -44,7 +30,7 @@ export declare function documentSlug(doc: RawDocument): string;
44
30
  * attachment whose document does not resolve is skipped, never fatal.
45
31
  */
46
32
  export declare function collectAttachedDocuments(source: DocumentSource, issueId: string): Promise<AttachedDocument[]>;
47
- /** The live `DocumentSource`, backed by a Linear bearer token. Plain fetch, no SDK. */
33
+ /** The live `DocumentSource`, backed by a Linear bearer token. Uses the typed SDK. */
48
34
  export declare function linearDocumentSource(token: string): DocumentSource;
49
35
  /**
50
36
  * Fetch the documents attached to a Linear issue as contract `AttachedDocument`s, ready to snapshot
@@ -1 +1 @@
1
- {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAIzD,qGAAqG;AACrG,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,+FAA+F;AAC/F,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACxD,4FAA4F;IAC5F,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,+FAA+F;IAC/F,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC3D;AAED;;;+FAG+F;AAC/F,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc9D;AAED;0FAC0F;AAC1F,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAQrD;AAeD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAuB7B;AAqBD,uFAAuF;AACvF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAgClE;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE7B"}
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,qGAAqG;AACrG,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,+FAA+F;AAC/F,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACxD,4FAA4F;IAC5F,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,+FAA+F;IAC/F,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC3D;AAED;;;+FAG+F;AAC/F,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc9D;AAED;0FAC0F;AAC1F,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAQrD;AAeD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAuB7B;AAED,sFAAsF;AACtF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAiClE;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE7B"}
package/dist/documents.js CHANGED
@@ -1,4 +1,18 @@
1
- const GRAPHQL_URL = "https://api.linear.app/graphql";
1
+ /**
2
+ * Fetch the Linear Documents attached to an issue, so the hub can snapshot their content into a run
3
+ * and surface it to the agent (the harness was webhook-payload-only before, so a document reached the
4
+ * agent only as a URL, never as content). This is a read against the same per-connection Linear token
5
+ * the hub already uses to post back; fetching content is data assembly, not control flow.
6
+ *
7
+ * Two sources, de-duped by document id:
8
+ * 1. Documents directly associated with the issue (`issue.documents`).
9
+ * 2. Documents linked via the issue's attachments whose `url` is a Linear document URL
10
+ * (`https://linear.app/<ws>/document/<...>-<slug>`), resolved through `document(id:)`.
11
+ *
12
+ * The `DocumentSource` seam keeps the assembly pure and unit-testable: the live source
13
+ * (`linearDocumentSource`) speaks GraphQL, while tests inject a fake.
14
+ */
15
+ import { LinearClient } from "@linear/sdk";
2
16
  /** The Linear-document slug from an attachment URL, or null when the URL is not a Linear document
3
17
  * link. Linear document URLs look like `https://linear.app/<ws>/document/<title-slug>-<slugId>`;
4
18
  * the canonical slug Linear's `document(id:)` accepts is the trailing token (the slugId), which is
@@ -76,42 +90,38 @@ export async function collectAttachedDocuments(source, issueId) {
76
90
  }
77
91
  return out;
78
92
  }
79
- /** Run one GraphQL query against Linear with a bearer token; throws on transport or GraphQL errors. */
80
- async function gql(token, query, variables) {
81
- const res = await fetch(GRAPHQL_URL, {
82
- method: "POST",
83
- headers: { "content-type": "application/json", authorization: token },
84
- body: JSON.stringify({ query, variables }),
85
- });
86
- if (!res.ok)
87
- throw new Error(`linear graphql ${res.status}`);
88
- const json = (await res.json().catch(() => ({})));
89
- if (json.errors?.length)
90
- throw new Error(json.errors.map((e) => e.message).join("; "));
91
- if (!json.data)
92
- throw new Error("linear graphql: empty response");
93
- return json.data;
94
- }
95
- const DOC_FIELDS = "id slugId title url content";
96
- /** The live `DocumentSource`, backed by a Linear bearer token. Plain fetch, no SDK. */
93
+ /** The live `DocumentSource`, backed by a Linear bearer token. Uses the typed SDK. */
97
94
  export function linearDocumentSource(token) {
95
+ const client = new LinearClient({ accessToken: token });
98
96
  return {
99
97
  async issueDocuments(issueId) {
100
98
  // Bounded, unpaginated cap: an issue with more than 50 attached documents (or 100 attachments
101
99
  // below) silently drops the overflow. That is a deliberate limit, not a bug; revisit with cursor
102
100
  // pagination if real issues approach it.
103
- const data = await gql(token, `query($id: String!){ issue(id:$id){ documents(first:50){ nodes{ ${DOC_FIELDS} } } } }`, { id: issueId });
104
- return data.issue?.documents?.nodes ?? [];
101
+ const issue = await client.issue(issueId);
102
+ const conn = await issue.documents({ first: 50 });
103
+ return conn.nodes.map((d) => ({
104
+ id: d.id,
105
+ slugId: d.slugId,
106
+ title: d.title,
107
+ url: d.url,
108
+ content: d.content ?? undefined,
109
+ }));
105
110
  },
106
111
  async issueAttachmentUrls(issueId) {
107
- const data = await gql(token, `query($id: String!){ issue(id:$id){ attachments(first:100){ nodes{ url } } } }`, { id: issueId });
108
- return (data.issue?.attachments?.nodes ?? [])
109
- .map((a) => a.url)
110
- .filter((u) => typeof u === "string" && u.length > 0);
112
+ const issue = await client.issue(issueId);
113
+ const conn = await issue.attachments({ first: 100 });
114
+ return conn.nodes.map((a) => a.url).filter((u) => u.length > 0);
111
115
  },
112
116
  async documentBySlug(slug) {
113
- const data = await gql(token, `query($id: String!){ document(id:$id){ ${DOC_FIELDS} } }`, { id: slug });
114
- return data.document ?? null;
117
+ const doc = await client.document(slug);
118
+ return {
119
+ id: doc.id,
120
+ slugId: doc.slugId,
121
+ title: doc.title,
122
+ url: doc.url,
123
+ content: doc.content ?? undefined,
124
+ };
115
125
  },
116
126
  };
117
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"documents.js","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"documents.js","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Format a tool call into Linear's action-activity vocabulary (DHK-382): a human verb in
3
+ * `action` and a clean, humanised input in `parameter`. Linear renders these as
4
+ * "<action> · <parameter>" (e.g. "Ran · grep -rn ..."), so the agent session reads as a
5
+ * sequence of verbs instead of a wall of `ToolName {raw JSON args}`.
6
+ *
7
+ * `inputText` is the edge's bounded preview of the tool input: normally a JSON object string
8
+ * (`clip(JSON.stringify(input))`), but it may be TRUNCATED (over the edge's ~500-char cap) or
9
+ * otherwise malformed. Parsing is defensive: a preview that will not parse never throws and
10
+ * never falls through to raw JSON. Known single-field tools still salvage their primary field
11
+ * from a truncated preview; anything unrecoverable degrades to the bare verb.
12
+ */
13
+ /** A tool call rendered as Linear's `{ action, parameter }` pair. */
14
+ export interface ToolAction {
15
+ action: string;
16
+ parameter: string;
17
+ }
18
+ /**
19
+ * Map a `(toolName, inputPreview)` pair to a verb plus a clean parameter. Total and defensive:
20
+ * every branch returns a `{ action, parameter }`, and the parameter is only ever a humanised
21
+ * field value (never the raw JSON), so a malformed or oversized preview degrades to the bare verb.
22
+ */
23
+ export declare function formatToolAction(tool: string | undefined, inputText: string | undefined): ToolAction;
24
+ export declare function formatToolResult(tool: string | undefined, output: string | undefined): string;
25
+ //# sourceMappingURL=format-action.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-action.d.ts","sourceRoot":"","sources":["../src/format-action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,qEAAqE;AACrE,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AA2FD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAgDpG;AAkGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAmB7F"}