@compilr-dev/agents 0.2.0 → 0.3.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/agent.d.ts CHANGED
@@ -35,10 +35,18 @@ export type AgentEvent = {
35
35
  type: 'tool_start';
36
36
  name: string;
37
37
  input: Record<string, unknown>;
38
+ toolUseId: string;
39
+ } | {
40
+ type: 'tool_output';
41
+ toolUseId: string;
42
+ toolName: string;
43
+ output: string;
44
+ stream?: 'stdout' | 'stderr';
38
45
  } | {
39
46
  type: 'tool_end';
40
47
  name: string;
41
48
  result: ToolExecutionResult;
49
+ toolUseId: string;
42
50
  } | {
43
51
  type: 'iteration_end';
44
52
  iteration: number;
package/dist/agent.js CHANGED
@@ -1621,13 +1621,34 @@ export class Agent {
1621
1621
  messages.push(assistantMsg);
1622
1622
  newMessages.push(assistantMsg);
1623
1623
  // Execute tools and add results
1624
- for (const toolUse of toolUses) {
1625
- // Check for abort before each tool
1624
+ // Check if we can parallelize - only parallelize tools marked as parallel-safe
1625
+ const parallelTools = toolUses.filter((tu) => {
1626
+ const tool = this.toolRegistry.get(tu.name);
1627
+ return tool?.parallel === true;
1628
+ });
1629
+ const canParallelize = parallelTools.length > 1 && parallelTools.length === toolUses.length;
1630
+ // Helper to execute a single tool with all checks
1631
+ const executeSingleTool = async (toolUse) => {
1632
+ // Check for abort
1626
1633
  if (signal?.aborted) {
1627
- aborted = true;
1628
- break;
1634
+ return {
1635
+ result: { success: false, error: 'Aborted' },
1636
+ toolResultMsg: {
1637
+ role: 'user',
1638
+ content: [
1639
+ { type: 'tool_result', toolUseId: toolUse.id, content: 'Aborted', isError: true },
1640
+ ],
1641
+ },
1642
+ skipped: true,
1643
+ aborted: true,
1644
+ };
1629
1645
  }
1630
- emit({ type: 'tool_start', name: toolUse.name, input: toolUse.input });
1646
+ emit({
1647
+ type: 'tool_start',
1648
+ name: toolUse.name,
1649
+ input: toolUse.input,
1650
+ toolUseId: toolUse.id,
1651
+ });
1631
1652
  let result;
1632
1653
  // Check permissions before execution
1633
1654
  if (this.permissionManager) {
@@ -1646,27 +1667,23 @@ export class Agent {
1646
1667
  success: false,
1647
1668
  error: `Permission denied: ${permResult.reason ?? 'Tool execution not allowed'}`,
1648
1669
  };
1649
- // Emit tool_end and record the tool call
1650
- emit({ type: 'tool_end', name: toolUse.name, result });
1651
- const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
1652
- toolCalls.push(toolCallEntry);
1653
- iterationToolCalls.push(toolCallEntry);
1654
- // CRITICAL: Add tool_result message to messages array
1655
- // Claude API requires every tool_use to have a corresponding tool_result
1656
- const toolResultMsg = {
1657
- role: 'user',
1658
- content: [
1659
- {
1660
- type: 'tool_result',
1661
- toolUseId: toolUse.id,
1662
- content: `Error: ${result.error ?? 'Permission denied'}`,
1663
- isError: true,
1664
- },
1665
- ],
1670
+ emit({ type: 'tool_end', name: toolUse.name, result, toolUseId: toolUse.id });
1671
+ return {
1672
+ result,
1673
+ toolResultMsg: {
1674
+ role: 'user',
1675
+ content: [
1676
+ {
1677
+ type: 'tool_result',
1678
+ toolUseId: toolUse.id,
1679
+ content: `Error: ${result.error ?? 'Permission denied'}`,
1680
+ isError: true,
1681
+ },
1682
+ ],
1683
+ },
1684
+ skipped: true,
1685
+ aborted: false,
1666
1686
  };
1667
- messages.push(toolResultMsg);
1668
- newMessages.push(toolResultMsg);
1669
- continue;
1670
1687
  }
1671
1688
  emit({ type: 'permission_granted', toolName: toolUse.name, level: permResult.level });
1672
1689
  }
@@ -1676,37 +1693,31 @@ export class Agent {
1676
1693
  if (guardrailResult.triggered) {
1677
1694
  emit({ type: 'guardrail_triggered', result: guardrailResult });
1678
1695
  if (!proceed) {
1679
- // Guardrail blocked the execution
1680
1696
  const message = guardrailResult.guardrail?.message ?? 'Operation blocked by guardrail';
1681
1697
  emit({ type: 'guardrail_blocked', result: guardrailResult, message });
1682
1698
  result = {
1683
1699
  success: false,
1684
1700
  error: `Guardrail blocked: ${message}`,
1685
1701
  };
1686
- // Emit tool_end and record the tool call
1687
- emit({ type: 'tool_end', name: toolUse.name, result });
1688
- const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
1689
- toolCalls.push(toolCallEntry);
1690
- iterationToolCalls.push(toolCallEntry);
1691
- // CRITICAL: Add tool_result message to messages array
1692
- // Claude API requires every tool_use to have a corresponding tool_result
1693
- const toolResultMsg = {
1694
- role: 'user',
1695
- content: [
1696
- {
1697
- type: 'tool_result',
1698
- toolUseId: toolUse.id,
1699
- content: `Error: ${result.error ?? 'Blocked by guardrail'}`,
1700
- isError: true,
1701
- },
1702
- ],
1702
+ emit({ type: 'tool_end', name: toolUse.name, result, toolUseId: toolUse.id });
1703
+ return {
1704
+ result,
1705
+ toolResultMsg: {
1706
+ role: 'user',
1707
+ content: [
1708
+ {
1709
+ type: 'tool_result',
1710
+ toolUseId: toolUse.id,
1711
+ content: `Error: ${result.error ?? 'Blocked by guardrail'}`,
1712
+ isError: true,
1713
+ },
1714
+ ],
1715
+ },
1716
+ skipped: true,
1717
+ aborted: false,
1703
1718
  };
1704
- messages.push(toolResultMsg);
1705
- newMessages.push(toolResultMsg);
1706
- continue;
1707
1719
  }
1708
1720
  else if (guardrailResult.action === 'warn') {
1709
- // Log warning and continue
1710
1721
  const message = guardrailResult.guardrail?.message ?? 'Warning from guardrail';
1711
1722
  emit({ type: 'guardrail_warning', result: guardrailResult, message });
1712
1723
  }
@@ -1722,20 +1733,45 @@ export class Agent {
1722
1733
  });
1723
1734
  if (!beforeToolResult.proceed) {
1724
1735
  result = beforeToolResult.skipResult ?? { success: false, error: 'Skipped by hook' };
1725
- emit({ type: 'tool_end', name: toolUse.name, result });
1726
- const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
1727
- toolCalls.push(toolCallEntry);
1728
- iterationToolCalls.push(toolCallEntry);
1729
- continue;
1736
+ emit({ type: 'tool_end', name: toolUse.name, result, toolUseId: toolUse.id });
1737
+ return {
1738
+ result,
1739
+ toolResultMsg: {
1740
+ role: 'user',
1741
+ content: [
1742
+ {
1743
+ type: 'tool_result',
1744
+ toolUseId: toolUse.id,
1745
+ content: result.success
1746
+ ? JSON.stringify(result.result)
1747
+ : `Error: ${result.error ?? 'Unknown error'}`,
1748
+ isError: !result.success,
1749
+ },
1750
+ ],
1751
+ },
1752
+ skipped: true,
1753
+ aborted: false,
1754
+ };
1730
1755
  }
1731
1756
  toolInput = beforeToolResult.input;
1732
1757
  }
1733
1758
  const toolStartTime = Date.now();
1734
1759
  try {
1735
- result = await this.toolRegistry.execute(toolUse.name, toolInput);
1760
+ const toolContext = {
1761
+ toolUseId: toolUse.id,
1762
+ onOutput: (output, stream) => {
1763
+ emit({
1764
+ type: 'tool_output',
1765
+ toolUseId: toolUse.id,
1766
+ toolName: toolUse.name,
1767
+ output,
1768
+ stream,
1769
+ });
1770
+ },
1771
+ };
1772
+ result = await this.toolRegistry.execute(toolUse.name, toolInput, toolContext);
1736
1773
  }
1737
1774
  catch (error) {
1738
- // Run onError hooks for tool errors (thrown exceptions)
1739
1775
  if (this.hooksManager && error instanceof Error) {
1740
1776
  const errorResult = await this.hooksManager.runOnError({
1741
1777
  ...hookContext,
@@ -1760,8 +1796,7 @@ export class Agent {
1760
1796
  };
1761
1797
  }
1762
1798
  }
1763
- // Run onError hooks for failed tool results (not thrown, but returned errors)
1764
- // The tool registry catches errors internally and returns { success: false }
1799
+ // Run onError hooks for failed tool results
1765
1800
  if (this.hooksManager && !result.success) {
1766
1801
  const syntheticError = new Error(result.error);
1767
1802
  const errorResult = await this.hooksManager.runOnError({
@@ -1784,89 +1819,116 @@ export class Agent {
1784
1819
  durationMs: Date.now() - toolStartTime,
1785
1820
  });
1786
1821
  }
1787
- emit({ type: 'tool_end', name: toolUse.name, result });
1788
- // Tool loop detection: check for consecutive identical calls
1789
- if (this.maxConsecutiveToolCalls > 0) {
1790
- const currentHash = hashToolCall(toolUse.name, toolUse.input);
1791
- if (currentHash === lastToolCallHash) {
1792
- consecutiveIdenticalCalls++;
1793
- if (consecutiveIdenticalCalls >= this.maxConsecutiveToolCalls) {
1794
- throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
1795
- }
1796
- // Emit warning before throwing
1797
- emit({
1798
- type: 'tool_loop_warning',
1799
- toolName: toolUse.name,
1800
- consecutiveCalls: consecutiveIdenticalCalls,
1801
- });
1802
- }
1803
- else {
1804
- // Different tool call, reset counter
1805
- lastToolCallHash = currentHash;
1806
- consecutiveIdenticalCalls = 1;
1807
- }
1808
- }
1809
- const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
1810
- toolCalls.push(toolCallEntry);
1811
- iterationToolCalls.push(toolCallEntry);
1822
+ emit({ type: 'tool_end', name: toolUse.name, result, toolUseId: toolUse.id });
1812
1823
  // Build tool result content
1813
1824
  let toolResultContent = result.success
1814
1825
  ? JSON.stringify(result.result)
1815
1826
  : `Error: ${result.error ?? 'Unknown error'}`;
1816
- // Context management: pre-flight check and filtering for tool results
1817
- if (this.contextManager && this.autoContextManagement) {
1827
+ // Context management (only for sequential - parallel handles this after)
1828
+ if (!canParallelize && this.contextManager && this.autoContextManagement) {
1818
1829
  const estimatedTokens = this.contextManager.estimateTokens(toolResultContent);
1819
1830
  const preflight = this.contextManager.canAddContent(estimatedTokens, 'toolResults');
1820
1831
  if (!preflight.allowed) {
1821
1832
  if (preflight.action === 'reject') {
1822
- // Content too large - filter it
1823
1833
  const filtered = this.contextManager.filterContent(toolResultContent, 'tool_result');
1824
1834
  toolResultContent = filtered.content;
1825
1835
  }
1826
- else if (preflight.action === 'compact') {
1827
- // Category budget exceeded - compact the category first
1828
- const tokensBefore = this.contextManager.getTokenCount();
1829
- const compactResult = await this.contextManager.compactCategory(messages, 'toolResults', (content, index) => Promise.resolve(`[compacted_tool_result_${String(index)}]`));
1830
- messages = compactResult.messages;
1831
- // Inject restoration hints after compaction
1832
- this.injectRestorationHints(messages);
1836
+ // Note: compact/summarize actions are complex with parallel execution
1837
+ // For now, only filter is applied during parallel; full context mgmt happens after
1838
+ }
1839
+ const finalTokens = this.contextManager.estimateTokens(toolResultContent);
1840
+ this.contextManager.addToCategory('toolResults', finalTokens);
1841
+ }
1842
+ return {
1843
+ result,
1844
+ toolResultMsg: {
1845
+ role: 'user',
1846
+ content: [
1847
+ {
1848
+ type: 'tool_result',
1849
+ toolUseId: toolUse.id,
1850
+ content: toolResultContent,
1851
+ isError: !result.success,
1852
+ },
1853
+ ],
1854
+ },
1855
+ skipped: false,
1856
+ aborted: false,
1857
+ };
1858
+ };
1859
+ // Execute tools - parallel if all are parallel-safe, otherwise sequential
1860
+ if (canParallelize) {
1861
+ // Parallel execution
1862
+ const results = await Promise.all(toolUses.map((tu) => executeSingleTool(tu)));
1863
+ for (let i = 0; i < toolUses.length; i++) {
1864
+ const toolUse = toolUses[i];
1865
+ const { result, toolResultMsg, aborted: wasAborted } = results[i];
1866
+ if (wasAborted) {
1867
+ aborted = true;
1868
+ break;
1869
+ }
1870
+ // Tool loop detection (still applies per-tool)
1871
+ if (this.maxConsecutiveToolCalls > 0) {
1872
+ const currentHash = hashToolCall(toolUse.name, toolUse.input);
1873
+ if (currentHash === lastToolCallHash) {
1874
+ consecutiveIdenticalCalls++;
1875
+ if (consecutiveIdenticalCalls >= this.maxConsecutiveToolCalls) {
1876
+ throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
1877
+ }
1833
1878
  emit({
1834
- type: 'context_compacted',
1835
- tokensBefore,
1836
- tokensAfter: this.contextManager.getTokenCount(),
1879
+ type: 'tool_loop_warning',
1880
+ toolName: toolUse.name,
1881
+ consecutiveCalls: consecutiveIdenticalCalls,
1837
1882
  });
1838
1883
  }
1839
- else if (preflight.action === 'summarize') {
1840
- // Approaching context limit - summarize
1841
- const { messages: summarized, result: sumResult } = await this.contextManager.summarizeWithRetry(messages, (msgs) => this.generateSummary(msgs));
1842
- messages = summarized;
1843
- // Inject restoration hints after summarization
1844
- this.injectRestorationHints(messages);
1884
+ else {
1885
+ lastToolCallHash = currentHash;
1886
+ consecutiveIdenticalCalls = 1;
1887
+ }
1888
+ }
1889
+ const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
1890
+ toolCalls.push(toolCallEntry);
1891
+ iterationToolCalls.push(toolCallEntry);
1892
+ messages.push(toolResultMsg);
1893
+ newMessages.push(toolResultMsg);
1894
+ }
1895
+ }
1896
+ else {
1897
+ // Sequential execution (original loop, but using the helper)
1898
+ for (const toolUse of toolUses) {
1899
+ const { result, toolResultMsg, skipped, aborted: wasAborted, } = await executeSingleTool(toolUse);
1900
+ if (wasAborted) {
1901
+ aborted = true;
1902
+ break;
1903
+ }
1904
+ // Tool loop detection
1905
+ if (this.maxConsecutiveToolCalls > 0) {
1906
+ const currentHash = hashToolCall(toolUse.name, toolUse.input);
1907
+ if (currentHash === lastToolCallHash) {
1908
+ consecutiveIdenticalCalls++;
1909
+ if (consecutiveIdenticalCalls >= this.maxConsecutiveToolCalls) {
1910
+ throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
1911
+ }
1845
1912
  emit({
1846
- type: 'context_summarized',
1847
- tokensBefore: sumResult.originalTokens,
1848
- tokensAfter: sumResult.summaryTokens,
1849
- rounds: sumResult.rounds,
1913
+ type: 'tool_loop_warning',
1914
+ toolName: toolUse.name,
1915
+ consecutiveCalls: consecutiveIdenticalCalls,
1850
1916
  });
1851
1917
  }
1918
+ else {
1919
+ lastToolCallHash = currentHash;
1920
+ consecutiveIdenticalCalls = 1;
1921
+ }
1922
+ }
1923
+ const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
1924
+ toolCalls.push(toolCallEntry);
1925
+ iterationToolCalls.push(toolCallEntry);
1926
+ messages.push(toolResultMsg);
1927
+ newMessages.push(toolResultMsg);
1928
+ if (skipped) {
1929
+ continue;
1852
1930
  }
1853
- // Track tool result tokens
1854
- const finalTokens = this.contextManager.estimateTokens(toolResultContent);
1855
- this.contextManager.addToCategory('toolResults', finalTokens);
1856
1931
  }
1857
- const toolResultMsg = {
1858
- role: 'user',
1859
- content: [
1860
- {
1861
- type: 'tool_result',
1862
- toolUseId: toolUse.id,
1863
- content: toolResultContent,
1864
- isError: !result.success,
1865
- },
1866
- ],
1867
- };
1868
- messages.push(toolResultMsg);
1869
- newMessages.push(toolResultMsg);
1870
1932
  }
1871
1933
  if (aborted) {
1872
1934
  break;
@@ -1904,7 +1966,11 @@ export class Agent {
1904
1966
  addedIterations: result,
1905
1967
  });
1906
1968
  }
1907
- // If false or 0, loop will exit naturally on next condition check
1969
+ else {
1970
+ // User chose to stop - set aborted to prevent error throw
1971
+ aborted = true;
1972
+ emit({ type: 'done', response: finalResponse });
1973
+ }
1908
1974
  }
1909
1975
  }
1910
1976
  // Check if we hit max iterations without completing
@@ -66,6 +66,40 @@ export declare class AnchorManager {
66
66
  * Clear anchors based on criteria
67
67
  */
68
68
  clear(options?: AnchorClearOptions): number;
69
+ /**
70
+ * Get all anchors for a specific project
71
+ *
72
+ * @param projectId - The project ID to filter by
73
+ * @param options - Additional query options (priority, scope, tags)
74
+ */
75
+ getByProject(projectId: string, options?: Omit<AnchorQueryOptions, 'projectId' | 'globalOnly'>): Anchor[];
76
+ /**
77
+ * Get all global anchors (those without a projectId)
78
+ *
79
+ * @param options - Additional query options (priority, scope, tags)
80
+ */
81
+ getGlobal(options?: Omit<AnchorQueryOptions, 'projectId' | 'globalOnly'>): Anchor[];
82
+ /**
83
+ * Clear all anchors for a specific project
84
+ *
85
+ * @param projectId - The project ID
86
+ * @returns Number of anchors removed
87
+ */
88
+ clearByProject(projectId: string): number;
89
+ /**
90
+ * Get list of unique project IDs that have anchors
91
+ */
92
+ getProjectIds(): string[];
93
+ /**
94
+ * Check if a project has any anchors
95
+ */
96
+ hasProjectAnchors(projectId: string): boolean;
97
+ /**
98
+ * Get anchor counts grouped by project
99
+ *
100
+ * @returns Map where keys are project IDs (null for global) and values are counts
101
+ */
102
+ getProjectStats(): Map<string | null, number>;
69
103
  /**
70
104
  * Get the current number of anchors
71
105
  */
@@ -104,6 +104,7 @@ export class AnchorManager {
104
104
  expiresAt: input.expiresAt,
105
105
  tags: input.tags,
106
106
  metadata: input.metadata,
107
+ projectId: input.projectId,
107
108
  };
108
109
  // Store the anchor
109
110
  this.anchors.set(anchor.id, anchor);
@@ -149,6 +150,14 @@ export class AnchorManager {
149
150
  return tagsFilter.every((tag) => anchorTags.includes(tag));
150
151
  });
151
152
  }
153
+ // Filter by project ID
154
+ if (options?.projectId) {
155
+ anchors = anchors.filter((a) => a.projectId === options.projectId);
156
+ }
157
+ // Filter to global only (no projectId)
158
+ if (options?.globalOnly) {
159
+ anchors = anchors.filter((a) => !a.projectId);
160
+ }
152
161
  // Filter out expired unless requested
153
162
  if (!options?.includeExpired) {
154
163
  anchors = anchors.filter((a) => !this.isExpired(a));
@@ -204,6 +213,14 @@ export class AnchorManager {
204
213
  if (options?.expiredOnly && !this.isExpired(anchor)) {
205
214
  continue;
206
215
  }
216
+ // Filter by project ID
217
+ if (options?.projectId && anchor.projectId !== options.projectId) {
218
+ continue;
219
+ }
220
+ // Filter to global only (no projectId)
221
+ if (options?.globalOnly && anchor.projectId) {
222
+ continue;
223
+ }
207
224
  toRemove.push(id);
208
225
  }
209
226
  for (const id of toRemove) {
@@ -212,6 +229,74 @@ export class AnchorManager {
212
229
  }
213
230
  return removed;
214
231
  }
232
+ // ─────────────────────────────────────────────────────────────────────────────
233
+ // Project-scoped methods
234
+ // ─────────────────────────────────────────────────────────────────────────────
235
+ /**
236
+ * Get all anchors for a specific project
237
+ *
238
+ * @param projectId - The project ID to filter by
239
+ * @param options - Additional query options (priority, scope, tags)
240
+ */
241
+ getByProject(projectId, options) {
242
+ return this.getAll({ ...options, projectId });
243
+ }
244
+ /**
245
+ * Get all global anchors (those without a projectId)
246
+ *
247
+ * @param options - Additional query options (priority, scope, tags)
248
+ */
249
+ getGlobal(options) {
250
+ return this.getAll({ ...options, globalOnly: true });
251
+ }
252
+ /**
253
+ * Clear all anchors for a specific project
254
+ *
255
+ * @param projectId - The project ID
256
+ * @returns Number of anchors removed
257
+ */
258
+ clearByProject(projectId) {
259
+ return this.clear({ projectId });
260
+ }
261
+ /**
262
+ * Get list of unique project IDs that have anchors
263
+ */
264
+ getProjectIds() {
265
+ const projectIds = new Set();
266
+ for (const anchor of this.anchors.values()) {
267
+ if (anchor.projectId && !this.isExpired(anchor)) {
268
+ projectIds.add(anchor.projectId);
269
+ }
270
+ }
271
+ return Array.from(projectIds);
272
+ }
273
+ /**
274
+ * Check if a project has any anchors
275
+ */
276
+ hasProjectAnchors(projectId) {
277
+ for (const anchor of this.anchors.values()) {
278
+ if (anchor.projectId === projectId && !this.isExpired(anchor)) {
279
+ return true;
280
+ }
281
+ }
282
+ return false;
283
+ }
284
+ /**
285
+ * Get anchor counts grouped by project
286
+ *
287
+ * @returns Map where keys are project IDs (null for global) and values are counts
288
+ */
289
+ getProjectStats() {
290
+ const stats = new Map();
291
+ for (const anchor of this.anchors.values()) {
292
+ if (this.isExpired(anchor))
293
+ continue;
294
+ const key = anchor.projectId ?? null;
295
+ stats.set(key, (stats.get(key) ?? 0) + 1);
296
+ }
297
+ return stats;
298
+ }
299
+ // ─────────────────────────────────────────────────────────────────────────────
215
300
  /**
216
301
  * Get the current number of anchors
217
302
  */
@@ -391,6 +476,7 @@ export class AnchorManager {
391
476
  expiresAt: a.expiresAt?.toISOString(),
392
477
  tags: a.tags,
393
478
  metadata: a.metadata,
479
+ projectId: a.projectId,
394
480
  }));
395
481
  fs.writeFileSync(expandedPath, JSON.stringify(serialized, null, 2));
396
482
  this.emit('anchor:persisted', undefined, `Saved ${String(serialized.length)} persistent anchors`);
@@ -57,6 +57,11 @@ export interface Anchor {
57
57
  * Optional metadata for custom use
58
58
  */
59
59
  metadata?: Record<string, unknown>;
60
+ /**
61
+ * Optional project association for project-scoped anchors
62
+ * If not provided, the anchor is considered "global"
63
+ */
64
+ projectId?: string;
60
65
  }
61
66
  /**
62
67
  * Input for adding a new anchor (id and createdAt are auto-generated if not provided)
@@ -69,6 +74,10 @@ export interface AnchorInput {
69
74
  expiresAt?: Date;
70
75
  tags?: string[];
71
76
  metadata?: Record<string, unknown>;
77
+ /**
78
+ * Optional project association for project-scoped anchors
79
+ */
80
+ projectId?: string;
72
81
  }
73
82
  /**
74
83
  * Options for querying anchors
@@ -90,6 +99,16 @@ export interface AnchorQueryOptions {
90
99
  * Include expired temporary anchors (default: false)
91
100
  */
92
101
  includeExpired?: boolean;
102
+ /**
103
+ * Filter by project ID
104
+ * Only returns anchors associated with this project
105
+ */
106
+ projectId?: string;
107
+ /**
108
+ * If true, only return global anchors (those without a projectId)
109
+ * Cannot be used together with projectId
110
+ */
111
+ globalOnly?: boolean;
93
112
  }
94
113
  /**
95
114
  * Options for clearing anchors
@@ -107,6 +126,15 @@ export interface AnchorClearOptions {
107
126
  * Clear only expired anchors
108
127
  */
109
128
  expiredOnly?: boolean;
129
+ /**
130
+ * Clear only anchors associated with this project
131
+ */
132
+ projectId?: string;
133
+ /**
134
+ * If true, only clear global anchors (those without a projectId)
135
+ * Cannot be used together with projectId
136
+ */
137
+ globalOnly?: boolean;
110
138
  }
111
139
  /**
112
140
  * Configuration for the AnchorManager
@@ -148,6 +176,7 @@ export interface SerializedAnchor {
148
176
  expiresAt?: string;
149
177
  tags?: string[];
150
178
  metadata?: Record<string, unknown>;
179
+ projectId?: string;
151
180
  }
152
181
  /**
153
182
  * Event types for anchor operations