@mrpatronz/nexusflow 2.2.0 → 2.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.
Files changed (135) hide show
  1. package/README.md +80 -17
  2. package/dist/commands/ui.d.ts +1 -0
  3. package/dist/commands/ui.d.ts.map +1 -1
  4. package/dist/commands/ui.js +24 -13
  5. package/dist/commands/ui.js.map +1 -1
  6. package/dist/commands/ui.test.d.ts +2 -0
  7. package/dist/commands/ui.test.d.ts.map +1 -0
  8. package/dist/commands/ui.test.js +58 -0
  9. package/dist/commands/ui.test.js.map +1 -0
  10. package/dist/core/workspace-skills-workflow.integration.test.d.ts +2 -0
  11. package/dist/core/workspace-skills-workflow.integration.test.d.ts.map +1 -0
  12. package/dist/core/workspace-skills-workflow.integration.test.js +162 -0
  13. package/dist/core/workspace-skills-workflow.integration.test.js.map +1 -0
  14. package/dist/core/worktree.test.js +1 -1
  15. package/dist/core/worktree.test.js.map +1 -1
  16. package/dist/generators/index.d.ts.map +1 -1
  17. package/dist/generators/index.js +4 -8
  18. package/dist/generators/index.js.map +1 -1
  19. package/dist/generators/skills-generator.d.ts +2 -2
  20. package/dist/generators/skills-generator.d.ts.map +1 -1
  21. package/dist/generators/skills-generator.js +84 -89
  22. package/dist/generators/skills-generator.js.map +1 -1
  23. package/dist/generators/skills-generator.test.d.ts +2 -0
  24. package/dist/generators/skills-generator.test.d.ts.map +1 -0
  25. package/dist/generators/skills-generator.test.js +79 -0
  26. package/dist/generators/skills-generator.test.js.map +1 -0
  27. package/dist/gui/assets/AgentsPage-BtrTgIyU.js +3 -0
  28. package/dist/gui/assets/{DashboardPage-CUspfCap.js → DashboardPage-CeUkMdxc.js} +1 -1
  29. package/dist/gui/assets/{GettingStartedPage-BqtgARZW.js → GettingStartedPage-YwJXpsc6.js} +1 -1
  30. package/dist/gui/assets/ProjectsPage-D8kEHb-m.js +1 -0
  31. package/dist/gui/assets/ScaffoldRepoInline-ij7JLzmx.js +1 -0
  32. package/dist/gui/assets/SettingsPage-BQhAP4go.js +1 -0
  33. package/dist/gui/assets/SkillsPage-DJ-dXfcC.js +6 -0
  34. package/dist/gui/assets/StartWorkPage-DGWZzF4H.js +1 -0
  35. package/dist/gui/assets/{StrategiesPage-D0Y6A4Tw.js → StrategiesPage-CLOjDRbX.js} +2 -2
  36. package/dist/gui/assets/WorkspaceLauncher-CjNI4vqn.js +1 -0
  37. package/dist/gui/assets/WorkspacesPage-MDzm2JUh.js +6 -0
  38. package/dist/gui/assets/alert-dialog-DjwFcE6b.js +1 -0
  39. package/dist/gui/assets/bot-wH0FKlic.js +1 -0
  40. package/dist/gui/assets/chevron-down-DDmhvRLr.js +1 -0
  41. package/dist/gui/assets/code-xml-Chu1p81J.js +1 -0
  42. package/dist/gui/assets/index-CfQEnn52.js +52 -0
  43. package/dist/gui/assets/index-k7inKaui.css +2 -0
  44. package/dist/gui/assets/{spinner-DXt6mnJk.js → spinner-BCEaGbib.js} +1 -1
  45. package/dist/gui/assets/terminal-gK6BMl3E.js +1 -0
  46. package/dist/gui/assets/{textarea-BLU5HXmZ.js → textarea-D3-elOAa.js} +1 -1
  47. package/dist/gui/assets/{useAriaLabelledBy-CjNlctb9.js → useAriaLabelledBy-DmwmZIqb.js} +1 -1
  48. package/dist/gui/assets/useCompositeListItem-CZnlZ-eF.js +1 -0
  49. package/dist/gui/assets/useValueChanged-BfI2s7NX.js +1 -0
  50. package/dist/gui/assets/zap-FQrDnrgb.js +1 -0
  51. package/dist/gui/index.html +6 -4
  52. package/dist/resources/agents-catalog.d.ts +7 -0
  53. package/dist/resources/agents-catalog.d.ts.map +1 -0
  54. package/dist/resources/agents-catalog.js +141 -0
  55. package/dist/resources/agents-catalog.js.map +1 -0
  56. package/dist/resources/agents-catalog.test.d.ts +2 -0
  57. package/dist/resources/agents-catalog.test.d.ts.map +1 -0
  58. package/dist/resources/agents-catalog.test.js +50 -0
  59. package/dist/resources/agents-catalog.test.js.map +1 -0
  60. package/dist/resources/codex-agent.d.ts +6 -0
  61. package/dist/resources/codex-agent.d.ts.map +1 -0
  62. package/dist/resources/codex-agent.js +55 -0
  63. package/dist/resources/codex-agent.js.map +1 -0
  64. package/dist/resources/codex-agent.test.d.ts +2 -0
  65. package/dist/resources/codex-agent.test.d.ts.map +1 -0
  66. package/dist/resources/codex-agent.test.js +27 -0
  67. package/dist/resources/codex-agent.test.js.map +1 -0
  68. package/dist/resources/contracts.d.ts +116 -0
  69. package/dist/resources/contracts.d.ts.map +1 -0
  70. package/dist/resources/contracts.js +103 -0
  71. package/dist/resources/contracts.js.map +1 -0
  72. package/dist/resources/fs-safety.d.ts +11 -0
  73. package/dist/resources/fs-safety.d.ts.map +1 -0
  74. package/dist/resources/fs-safety.js +77 -0
  75. package/dist/resources/fs-safety.js.map +1 -0
  76. package/dist/resources/materializer.d.ts +17 -0
  77. package/dist/resources/materializer.d.ts.map +1 -0
  78. package/dist/resources/materializer.js +439 -0
  79. package/dist/resources/materializer.js.map +1 -0
  80. package/dist/resources/materializer.test.d.ts +2 -0
  81. package/dist/resources/materializer.test.d.ts.map +1 -0
  82. package/dist/resources/materializer.test.js +149 -0
  83. package/dist/resources/materializer.test.js.map +1 -0
  84. package/dist/resources/service.d.ts +8 -0
  85. package/dist/resources/service.d.ts.map +1 -0
  86. package/dist/resources/service.js +45 -0
  87. package/dist/resources/service.js.map +1 -0
  88. package/dist/resources/service.test.d.ts +2 -0
  89. package/dist/resources/service.test.d.ts.map +1 -0
  90. package/dist/resources/service.test.js +58 -0
  91. package/dist/resources/service.test.js.map +1 -0
  92. package/dist/server.d.ts +5 -0
  93. package/dist/server.d.ts.map +1 -1
  94. package/dist/server.js +310 -4
  95. package/dist/server.js.map +1 -1
  96. package/dist/server.test.js +365 -5
  97. package/dist/server.test.js.map +1 -1
  98. package/dist/types.d.ts +107 -0
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/utils/resources.d.ts +2 -1
  101. package/dist/utils/resources.d.ts.map +1 -1
  102. package/dist/utils/resources.js +4 -3
  103. package/dist/utils/resources.js.map +1 -1
  104. package/dist/utils/session-finder.d.ts +6 -0
  105. package/dist/utils/session-finder.d.ts.map +1 -1
  106. package/dist/utils/session-finder.js +56 -0
  107. package/dist/utils/session-finder.js.map +1 -1
  108. package/dist/utils/session-finder.test.js +34 -1
  109. package/dist/utils/session-finder.test.js.map +1 -1
  110. package/dist/utils/skills-catalog.d.ts +77 -0
  111. package/dist/utils/skills-catalog.d.ts.map +1 -0
  112. package/dist/utils/skills-catalog.js +871 -0
  113. package/dist/utils/skills-catalog.js.map +1 -0
  114. package/dist/utils/skills-catalog.test.d.ts +2 -0
  115. package/dist/utils/skills-catalog.test.d.ts.map +1 -0
  116. package/dist/utils/skills-catalog.test.js +281 -0
  117. package/dist/utils/skills-catalog.test.js.map +1 -0
  118. package/dist/utils/workspace-launch.d.ts.map +1 -1
  119. package/dist/utils/workspace-launch.js +22 -1
  120. package/dist/utils/workspace-launch.js.map +1 -1
  121. package/dist/utils/workspace-launch.test.js +36 -2
  122. package/dist/utils/workspace-launch.test.js.map +1 -1
  123. package/package.json +4 -1
  124. package/dist/gui/assets/ProjectsPage-UBoQxI2G.js +0 -1
  125. package/dist/gui/assets/ScaffoldRepoInline-DqoourJe.js +0 -1
  126. package/dist/gui/assets/SettingsPage-BZl24tqD.js +0 -1
  127. package/dist/gui/assets/StartWorkPage-BGNFdBfj.js +0 -1
  128. package/dist/gui/assets/WorkspaceLauncher-Cxv0O_wt.js +0 -1
  129. package/dist/gui/assets/WorkspacesPage-Cvb1rOeh.js +0 -6
  130. package/dist/gui/assets/index-CllPiiFL.js +0 -52
  131. package/dist/gui/assets/index-DGCBhMAn.css +0 -2
  132. package/dist/gui/assets/useCompositeListItem-DWGFLKqA.js +0 -1
  133. /package/dist/{resources → static-resources}/workflows/plan-implement-review.md +0 -0
  134. /package/dist/{resources → static-resources}/workflows/research-verify.md +0 -0
  135. /package/dist/{resources → static-resources}/workflows/solo-developer.md +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materializer.test.js","sourceRoot":"","sources":["../../src/resources/materializer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare class ResourceSelectionError extends Error {
2
+ readonly missingSkills: string[];
3
+ readonly missingAgents: string[];
4
+ constructor(missingSkills: string[], missingAgents: string[]);
5
+ }
6
+ export declare function withResourceAdministrationLock<T>(operation: () => Promise<T>): Promise<T>;
7
+ export declare function validateResourceSelections(enabledSkills: string[], enabledAgents: string[]): Promise<void>;
8
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/resources/service.ts"],"names":[],"mappings":"AAQA,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,SAAgB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxC,SAAgB,aAAa,EAAE,MAAM,EAAE,CAAC;gBAE5B,aAAa,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE;CAU7D;AAED,wBAAsB,8BAA8B,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAgB/F;AAED,wBAAsB,0BAA0B,CAC9C,aAAa,EAAE,MAAM,EAAE,EACvB,aAAa,EAAE,MAAM,EAAE,GACtB,OAAO,CAAC,IAAI,CAAC,CASf"}
@@ -0,0 +1,45 @@
1
+ import * as path from 'node:path';
2
+ import { acquireLock, createMutationQueue } from '../core/locks.js';
3
+ import { getAllSkills, getNexusFlowHome } from '../utils/skills-catalog.js';
4
+ import { getAllAgents } from './agents-catalog.js';
5
+ const runResourceAdministration = createMutationQueue();
6
+ export class ResourceSelectionError extends Error {
7
+ missingSkills;
8
+ missingAgents;
9
+ constructor(missingSkills, missingAgents) {
10
+ const parts = [
11
+ missingSkills.length ? `skills: ${missingSkills.join(', ')}` : '',
12
+ missingAgents.length ? `agents: ${missingAgents.join(', ')}` : '',
13
+ ].filter(Boolean);
14
+ super(`Selected resources are not available (${parts.join('; ')}).`);
15
+ this.name = 'ResourceSelectionError';
16
+ this.missingSkills = missingSkills;
17
+ this.missingAgents = missingAgents;
18
+ }
19
+ }
20
+ export async function withResourceAdministrationLock(operation) {
21
+ return runResourceAdministration(async () => {
22
+ const release = await acquireLock(path.join(getNexusFlowHome(), '.locks', 'resource-administration.lock'), {
23
+ staleMs: 60_000,
24
+ timeoutMs: 15_000,
25
+ timeoutMessage: 'Timed out waiting for resource administration.',
26
+ });
27
+ try {
28
+ return await operation();
29
+ }
30
+ finally {
31
+ await release();
32
+ }
33
+ });
34
+ }
35
+ export async function validateResourceSelections(enabledSkills, enabledAgents) {
36
+ const [skills, agents] = await Promise.all([getAllSkills(), getAllAgents()]);
37
+ const skillIds = new Set(skills.map((skill) => skill.id));
38
+ const agentIds = new Set(agents.map((agent) => agent.id));
39
+ const missingSkills = [...new Set(enabledSkills)].filter((id) => !skillIds.has(id));
40
+ const missingAgents = [...new Set(enabledAgents)].filter((id) => !agentIds.has(id));
41
+ if (missingSkills.length || missingAgents.length) {
42
+ throw new ResourceSelectionError(missingSkills, missingAgents);
43
+ }
44
+ }
45
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/resources/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,yBAAyB,GAAG,mBAAmB,EAAE,CAAC;AAExD,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/B,aAAa,CAAW;IACxB,aAAa,CAAW;IAExC,YAAY,aAAuB,EAAE,aAAuB;QAC1D,MAAM,KAAK,GAAG;YACZ,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;YACjE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;SAClE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAClB,KAAK,CAAC,yCAAyC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrE,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACrC,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAI,SAA2B;IACjF,OAAO,yBAAyB,CAAC,KAAK,IAAI,EAAE;QAC1C,MAAM,OAAO,GAAG,MAAM,WAAW,CAC/B,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,8BAA8B,CAAC,EACvE;YACE,OAAO,EAAE,MAAM;YACf,SAAS,EAAE,MAAM;YACjB,cAAc,EAAE,gDAAgD;SACjE,CACF,CAAC;QACF,IAAI,CAAC;YACH,OAAO,MAAM,SAAS,EAAE,CAAC;QAC3B,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,aAAuB,EACvB,aAAuB;IAEvB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1D,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACpF,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACpF,IAAI,aAAa,CAAC,MAAM,IAAI,aAAa,CAAC,MAAM,EAAE,CAAC;QACjD,MAAM,IAAI,sBAAsB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IACjE,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.test.d.ts","sourceRoot":"","sources":["../../src/resources/service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,58 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import * as fs from 'node:fs/promises';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import fse from 'fs-extra';
6
+ import { saveAgent } from './agents-catalog.js';
7
+ import { validateResourceSelections, withResourceAdministrationLock, } from './service.js';
8
+ describe('resource administration service', () => {
9
+ let tempHome;
10
+ const originalHome = process.env.NEXUSFLOW_HOME;
11
+ beforeEach(async () => {
12
+ tempHome = await fs.mkdtemp(path.join(os.tmpdir(), 'nexusflow-resource-service-'));
13
+ process.env.NEXUSFLOW_HOME = tempHome;
14
+ });
15
+ afterEach(async () => {
16
+ if (originalHome === undefined)
17
+ delete process.env.NEXUSFLOW_HOME;
18
+ else
19
+ process.env.NEXUSFLOW_HOME = originalHome;
20
+ await fse.remove(tempHome);
21
+ });
22
+ it('rejects missing selections and accepts existing underscore-style agents', async () => {
23
+ await saveAgent({
24
+ name: 'docs_researcher',
25
+ category: 'general',
26
+ description: 'Use when researching docs.',
27
+ developerInstructions: 'Research official documentation.',
28
+ });
29
+ await expect(validateResourceSelections(['pr-review-toolkit'], ['docs_researcher'])).resolves.toBeUndefined();
30
+ await expect(validateResourceSelections(['missing-skill'], ['missing_agent'])).rejects.toMatchObject({
31
+ missingSkills: ['missing-skill'],
32
+ missingAgents: ['missing_agent'],
33
+ });
34
+ });
35
+ it('serializes administration operations under one shared boundary', async () => {
36
+ const events = [];
37
+ let releaseFirst;
38
+ const firstGate = new Promise((resolve) => { releaseFirst = resolve; });
39
+ let firstStarted;
40
+ const started = new Promise((resolve) => { firstStarted = resolve; });
41
+ const first = withResourceAdministrationLock(async () => {
42
+ events.push('first-start');
43
+ firstStarted();
44
+ await firstGate;
45
+ events.push('first-end');
46
+ });
47
+ await started;
48
+ const second = withResourceAdministrationLock(async () => {
49
+ events.push('second-start');
50
+ events.push('second-end');
51
+ });
52
+ expect(events).toEqual(['first-start']);
53
+ releaseFirst();
54
+ await Promise.all([first, second]);
55
+ expect(events).toEqual(['first-start', 'first-end', 'second-start', 'second-end']);
56
+ });
57
+ });
58
+ //# sourceMappingURL=service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.test.js","sourceRoot":"","sources":["../../src/resources/service.test.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -23,6 +23,11 @@ export declare class PathAccessError extends Error {
23
23
  }
24
24
  /** Safe workspace directory for a route `:id`, contained within workspacesDir. */
25
25
  export declare function resolveWorkspacePath(workspacesDir: string, id: string): string;
26
+ /**
27
+ * Claude's documented `/desktop` transfer is narrower than its URI handler:
28
+ * it requires macOS or x64 Windows, a subscription login, and a usable CLI.
29
+ */
30
+ export declare function canOfferClaudeDesktopTransfer(platform?: NodeJS.Platform, architecture?: NodeJS.Architecture, env?: NodeJS.ProcessEnv, isCliConfigured?: () => boolean): boolean;
26
31
  /** Safe sub-repo path for a repo name, contained within the workspace. */
27
32
  export declare function resolveRepoPath(workspacePath: string, repoName: string): string;
28
33
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAqC5B,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAkDjF,eAAO,MAAM,GAAG,4EAAa,CAAC;AAc9B,mGAAmG;AACnG,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAE,GACvD,MAAM,GAAG,IAAI,CAUf;AAED,gFAAgF;AAChF,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;IAEhB,QAAQ,IAAI,OAAO;IAMnB,MAAM,IAAI,IAAI;IAId,QAAQ,IAAI,OAAO;CAG3B;AAuKD,yEAAyE;AACzE,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,SAA2B;CAI/C;AAcD,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9E;AA8BD,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE/E;AAWD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAc7D;AAuwDD,wBAAgB,WAAW,CACzB,IAAI,SAAO,EACX,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAClC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CA0BxC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AA0C5B,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA4EjF,eAAO,MAAM,GAAG,4EAAa,CAAC;AAc9B,mGAAmG;AACnG,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAE,GACvD,MAAM,GAAG,IAAI,CAUf;AAED,gFAAgF;AAChF,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;IAEhB,QAAQ,IAAI,OAAO;IAMnB,MAAM,IAAI,IAAI;IAId,QAAQ,IAAI,OAAO;CAG3B;AAuKD,yEAAyE;AACzE,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,SAA2B;CAI/C;AAcD,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9E;AAID;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,YAAY,GAAE,MAAM,CAAC,YAA2B,EAChD,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,eAAe,GAAE,MAAM,OAAmF,GACzG,OAAO,CAeT;AA8BD,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE/E;AAsDD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAc7D;AA4/DD,wBAAgB,WAAW,CACzB,IAAI,SAAO,EACX,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAClC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CA0BxC"}
package/dist/server.js CHANGED
@@ -30,7 +30,7 @@ import { generateContextFiles } from './generators/index.js';
30
30
  import { detectAIAssistants } from './utils/detect-ai.js';
31
31
  import { detectEditors } from './utils/detect-editors.js';
32
32
  import { buildWorkspaceLaunchPrompt, detectWorkspaceLaunchTargets, launchTargetIdForEditorCommand, launchWorkspaceTarget, } from './utils/workspace-launch.js';
33
- import { canOpenCodexSessionInWorkspace, findSessions, getSessionTranscript } from './utils/session-finder.js';
33
+ import { canOpenCodexSessionInWorkspace, canTransferClaudeSessionInWorkspace, findSessions, getSessionTranscript, } from './utils/session-finder.js';
34
34
  import { ProviderRegistry } from './agent/adapters.js';
35
35
  import { isValidSessionUuid } from './agent/session.js';
36
36
  import { getRepoStatus } from './utils/multi-git.js';
@@ -43,6 +43,10 @@ import { addSchedule, loadSchedules, nextDueAt, parseInterval, removeSchedule, r
43
43
  import { detectAllServices, detectOrchestrationTools, startServices, stopServices, startService, stopService, restartService, startOrchestrator, stopOrchestrator, tailLogFile, loadRunningState, getPm2List, } from './orchestration/index.js';
44
44
  import { checkForUpdates, getCurrentVersion, getToolsStatus } from './utils/update-check.js';
45
45
  import { getWorkflowTemplates, saveWorkflowTemplate, deleteWorkflowTemplate } from './utils/workflows.js';
46
+ import { getSkillCategories, saveSkillCategory, deleteSkillCategory, getAllSkills, saveSkill, deleteSkill, getWorkspaceSkillsConfig, saveWorkspaceSkillsConfig, WorkspaceResourceRevisionError, } from './utils/skills-catalog.js';
47
+ import { deleteAgent, getAllAgents, importAgentToml, saveAgent, } from './resources/agents-catalog.js';
48
+ import { ResourceConflictError } from './resources/materializer.js';
49
+ import { ResourceSelectionError, validateResourceSelections, withResourceAdministrationLock, } from './resources/service.js';
46
50
  import { suggestWorkflow } from './utils/workflow-advisor.js';
47
51
  // Resolve static files directory
48
52
  const __filename = fileURLToPath(import.meta.url);
@@ -282,6 +286,27 @@ function assertWithin(baseDir, target) {
282
286
  export function resolveWorkspacePath(workspacesDir, id) {
283
287
  return assertWithin(workspacesDir, path.join(workspacesDir, id));
284
288
  }
289
+ const DESKTOP_HANDOFF_SCAN_LIMIT = 20;
290
+ /**
291
+ * Claude's documented `/desktop` transfer is narrower than its URI handler:
292
+ * it requires macOS or x64 Windows, a subscription login, and a usable CLI.
293
+ */
294
+ export function canOfferClaudeDesktopTransfer(platform = process.platform, architecture = process.arch, env = process.env, isCliConfigured = () => ProviderRegistry.getProvider('claude-cli')?.isConfigured() === true) {
295
+ const supportedPlatform = platform === 'darwin' || (platform === 'win32' && architecture === 'x64');
296
+ const usesUnsupportedAuth = Boolean(env.ANTHROPIC_API_KEY
297
+ || env.ANTHROPIC_AUTH_TOKEN
298
+ || env.CLAUDE_CODE_USE_BEDROCK === '1'
299
+ || env.CLAUDE_CODE_USE_VERTEX === '1'
300
+ || env.CLAUDE_CODE_USE_FOUNDRY === '1');
301
+ if (!supportedPlatform || usesUnsupportedAuth)
302
+ return false;
303
+ try {
304
+ return isCliConfigured();
305
+ }
306
+ catch {
307
+ return false;
308
+ }
309
+ }
285
310
  /**
286
311
  * Resolve a launch path through the filesystem and require it to be the exact
287
312
  * workspace declared by its manifest. This prevents symlink escapes and stops
@@ -316,9 +341,49 @@ function errorResponse(c, error) {
316
341
  if (error instanceof PathAccessError) {
317
342
  return c.json({ error: error.message }, 400);
318
343
  }
344
+ if (error instanceof ResourceConflictError || error instanceof WorkspaceResourceRevisionError) {
345
+ return c.json({
346
+ error: error.message,
347
+ conflicts: error instanceof ResourceConflictError ? error.conflicts : undefined,
348
+ }, 409);
349
+ }
350
+ if (error instanceof ResourceSelectionError) {
351
+ return c.json({
352
+ error: error.message,
353
+ missingSkills: error.missingSkills,
354
+ missingAgents: error.missingAgents,
355
+ }, 400);
356
+ }
319
357
  const msg = error instanceof Error ? error.message : String(error);
320
358
  return c.json({ error: msg }, 500);
321
359
  }
360
+ async function resolveExactWorkspaceById(workspacesDir, id) {
361
+ const candidate = resolveWorkspacePath(workspacesDir, id);
362
+ try {
363
+ return await resolveExactLaunchWorkspace(workspacesDir, candidate);
364
+ }
365
+ catch (error) {
366
+ const code = typeof error === 'object' && error !== null && 'code' in error
367
+ ? String(error.code)
368
+ : undefined;
369
+ if (code === 'ENOENT')
370
+ return null;
371
+ throw error;
372
+ }
373
+ }
374
+ async function findResourceAssignments(workspacesDir, resourceId, kind) {
375
+ const assignments = [];
376
+ for (const workspace of await listWorkspaces(workspacesDir)) {
377
+ const workspacePath = await resolveExactWorkspaceById(workspacesDir, workspace.id);
378
+ if (!workspacePath)
379
+ continue;
380
+ const selection = await getWorkspaceSkillsConfig(workspacePath);
381
+ const enabled = kind === 'skill' ? selection.enabledSkills : selection.enabledAgents ?? [];
382
+ if (enabled.includes(resourceId))
383
+ assignments.push(workspace.id);
384
+ }
385
+ return assignments;
386
+ }
322
387
  /**
323
388
  * Restrict the self-update download to GitHub release hosts over HTTPS so a
324
389
  * cross-origin caller cannot make the server fetch and later execute an
@@ -1580,13 +1645,69 @@ app.post('/api/workspace/:id/resume', async (c) => {
1580
1645
  app.get('/api/workspace/:id/sessions', async (c) => {
1581
1646
  try {
1582
1647
  const id = decodeURIComponent(c.req.param('id'));
1648
+ const limitParam = c.req.query('limit');
1649
+ const desktopHandoffOnly = c.req.query('desktopHandoffOnly') === 'true';
1650
+ let limit;
1651
+ if (limitParam !== undefined) {
1652
+ limit = Number(limitParam);
1653
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 20) {
1654
+ return c.json({ error: 'Session limit must be an integer from 1 to 20.' }, 400);
1655
+ }
1656
+ }
1583
1657
  const config = await loadConfig();
1584
- const workspacePath = resolveWorkspacePath(config.workspacesDir, id);
1585
- const feature = await loadFeatureConfig(workspacePath);
1658
+ const requestedWorkspacePath = resolveWorkspacePath(config.workspacesDir, id);
1659
+ let workspacePath = requestedWorkspacePath;
1660
+ let feature;
1661
+ if (desktopHandoffOnly) {
1662
+ try {
1663
+ const exactWorkspacePath = await resolveExactLaunchWorkspace(config.workspacesDir, requestedWorkspacePath);
1664
+ if (!exactWorkspacePath) {
1665
+ return c.json({ error: 'Workspace configuration not found.' }, 404);
1666
+ }
1667
+ workspacePath = exactWorkspacePath;
1668
+ }
1669
+ catch (error) {
1670
+ if (error instanceof PathAccessError)
1671
+ throw error;
1672
+ return c.json({ error: 'Workspace configuration not found.' }, 404);
1673
+ }
1674
+ feature = await loadWorkspaceManifest(workspacePath);
1675
+ }
1676
+ else {
1677
+ feature = await loadFeatureConfig(workspacePath);
1678
+ }
1586
1679
  if (!feature) {
1587
1680
  return c.json({ error: 'Workspace configuration not found.' }, 404);
1588
1681
  }
1589
- const sessions = await findSessions(workspacePath, feature.repos);
1682
+ const discoveredSessions = await findSessions(workspacePath, feature.repos);
1683
+ if (!desktopHandoffOnly) {
1684
+ return c.json({ sessions: limit === undefined ? discoveredSessions : discoveredSessions.slice(0, limit) });
1685
+ }
1686
+ const requestedCount = limit ?? 3;
1687
+ const candidates = discoveredSessions
1688
+ .filter((session) => session.assistant === 'codex' || session.assistant === 'claude')
1689
+ .slice(0, DESKTOP_HANDOFF_SCAN_LIMIT);
1690
+ const sessions = [];
1691
+ const claudeTransferAvailable = candidates.some((session) => session.assistant === 'claude')
1692
+ && canOfferClaudeDesktopTransfer();
1693
+ for (const session of candidates) {
1694
+ if (session.assistant === 'codex' && await canOpenCodexSessionInWorkspace(workspacePath, feature.repos, session.id)) {
1695
+ sessions.push({
1696
+ ...session,
1697
+ desktopHandoff: { targetId: 'codex-desktop', method: 'direct' },
1698
+ });
1699
+ }
1700
+ else if (session.assistant === 'claude'
1701
+ && claudeTransferAvailable
1702
+ && await canTransferClaudeSessionInWorkspace(workspacePath, feature.repos, session.id)) {
1703
+ sessions.push({
1704
+ ...session,
1705
+ desktopHandoff: { targetId: 'claude-desktop', method: 'guided' },
1706
+ });
1707
+ }
1708
+ if (sessions.length === requestedCount)
1709
+ break;
1710
+ }
1590
1711
  return c.json({ sessions });
1591
1712
  }
1592
1713
  catch (error) {
@@ -1847,6 +1968,191 @@ and suffix it with:
1847
1968
  return errorResponse(c, error);
1848
1969
  }
1849
1970
  });
1971
+ // ─── Skills & Categories Catalog ──────────────────────────────────────────
1972
+ // Get all skill categories (built-in templates + user custom)
1973
+ app.get('/api/skills/categories', async (c) => {
1974
+ try {
1975
+ const categories = await getSkillCategories();
1976
+ return c.json({ categories });
1977
+ }
1978
+ catch (error) {
1979
+ return errorResponse(c, error);
1980
+ }
1981
+ });
1982
+ // Save or update a custom skill category
1983
+ app.post('/api/skills/categories', async (c) => {
1984
+ try {
1985
+ const body = await c.req.json();
1986
+ if (!body.name) {
1987
+ return c.json({ error: 'Category name is required.' }, 400);
1988
+ }
1989
+ const category = await saveSkillCategory(body);
1990
+ return c.json({ success: true, category });
1991
+ }
1992
+ catch (error) {
1993
+ return errorResponse(c, error);
1994
+ }
1995
+ });
1996
+ // Delete a custom skill category
1997
+ app.delete('/api/skills/categories/:id', async (c) => {
1998
+ try {
1999
+ const id = decodeURIComponent(c.req.param('id'));
2000
+ await deleteSkillCategory(id);
2001
+ return c.json({ success: true });
2002
+ }
2003
+ catch (error) {
2004
+ return errorResponse(c, error);
2005
+ }
2006
+ });
2007
+ // Get authoritative skills (built-ins + personal catalog). Workspace
2008
+ // materializations are deliberately never treated as catalog sources.
2009
+ app.get('/api/skills', async (c) => {
2010
+ try {
2011
+ const skills = await getAllSkills();
2012
+ return c.json({ skills });
2013
+ }
2014
+ catch (error) {
2015
+ return errorResponse(c, error);
2016
+ }
2017
+ });
2018
+ // Save or update a custom skill
2019
+ app.post('/api/skills', async (c) => {
2020
+ try {
2021
+ const body = await c.req.json();
2022
+ if (!body.name || !body.content) {
2023
+ return c.json({ error: 'Skill name and content are required.' }, 400);
2024
+ }
2025
+ const skill = await saveSkill(body);
2026
+ return c.json({ success: true, skill });
2027
+ }
2028
+ catch (error) {
2029
+ const message = error instanceof Error ? error.message : String(error);
2030
+ return c.json({ error: message }, 400);
2031
+ }
2032
+ });
2033
+ // Delete a custom skill
2034
+ app.delete('/api/skills/:id', async (c) => {
2035
+ try {
2036
+ const id = decodeURIComponent(c.req.param('id'));
2037
+ const config = await loadConfig();
2038
+ const assignments = await withResourceAdministrationLock(async () => {
2039
+ const currentAssignments = await findResourceAssignments(config.workspacesDir, id, 'skill');
2040
+ if (!currentAssignments.length)
2041
+ await deleteSkill(id);
2042
+ return currentAssignments;
2043
+ });
2044
+ if (assignments.length) {
2045
+ return c.json({
2046
+ error: `Unassign the skill from these workspaces before deleting it: ${assignments.join(', ')}`,
2047
+ workspaces: assignments,
2048
+ }, 409);
2049
+ }
2050
+ return c.json({ success: true });
2051
+ }
2052
+ catch (error) {
2053
+ return errorResponse(c, error);
2054
+ }
2055
+ });
2056
+ // Codex-native custom-agent catalog. Other harness agent formats are not
2057
+ // translated because their configuration and permission models are different.
2058
+ app.get('/api/agents', async (c) => {
2059
+ try {
2060
+ return c.json({ agents: await getAllAgents() });
2061
+ }
2062
+ catch (error) {
2063
+ return errorResponse(c, error);
2064
+ }
2065
+ });
2066
+ app.post('/api/agents', async (c) => {
2067
+ try {
2068
+ const agent = await saveAgent(await c.req.json());
2069
+ return c.json({ success: true, agent });
2070
+ }
2071
+ catch (error) {
2072
+ const message = error instanceof Error ? error.message : String(error);
2073
+ return c.json({ error: message }, 400);
2074
+ }
2075
+ });
2076
+ app.post('/api/agents/import', async (c) => {
2077
+ try {
2078
+ const body = await c.req.json();
2079
+ if (typeof body.toml !== 'string') {
2080
+ return c.json({ error: 'A TOML string is required.' }, 400);
2081
+ }
2082
+ const agent = await importAgentToml(body.toml, typeof body.category === 'string' ? body.category : 'general');
2083
+ return c.json({ success: true, agent });
2084
+ }
2085
+ catch (error) {
2086
+ const message = error instanceof Error ? error.message : String(error);
2087
+ return c.json({ error: message }, 400);
2088
+ }
2089
+ });
2090
+ app.delete('/api/agents/:id', async (c) => {
2091
+ try {
2092
+ const id = decodeURIComponent(c.req.param('id'));
2093
+ const config = await loadConfig();
2094
+ const assignments = await withResourceAdministrationLock(async () => {
2095
+ const currentAssignments = await findResourceAssignments(config.workspacesDir, id, 'agent');
2096
+ if (!currentAssignments.length)
2097
+ await deleteAgent(id);
2098
+ return currentAssignments;
2099
+ });
2100
+ if (assignments.length) {
2101
+ return c.json({
2102
+ error: `Unassign the agent from these workspaces before deleting it: ${assignments.join(', ')}`,
2103
+ workspaces: assignments,
2104
+ }, 409);
2105
+ }
2106
+ return c.json({ success: true });
2107
+ }
2108
+ catch (error) {
2109
+ return errorResponse(c, error);
2110
+ }
2111
+ });
2112
+ // Get workspace skills assignment config
2113
+ app.get('/api/skills/workspace/:id', async (c) => {
2114
+ try {
2115
+ const id = decodeURIComponent(c.req.param('id'));
2116
+ const config = await loadConfig();
2117
+ const wsPath = await resolveExactWorkspaceById(config.workspacesDir, id);
2118
+ if (!wsPath)
2119
+ return c.json({ error: 'Workspace not found.' }, 404);
2120
+ const skillsConfig = await getWorkspaceSkillsConfig(wsPath);
2121
+ return c.json({ config: skillsConfig });
2122
+ }
2123
+ catch (error) {
2124
+ return errorResponse(c, error);
2125
+ }
2126
+ });
2127
+ // Save workspace skills assignment config
2128
+ app.post('/api/skills/workspace/:id/assign', async (c) => {
2129
+ try {
2130
+ const id = decodeURIComponent(c.req.param('id'));
2131
+ const body = await c.req.json();
2132
+ if (!Array.isArray(body.enabledSkills) || !Array.isArray(body.enabledAgents)) {
2133
+ return c.json({ error: 'enabledSkills and enabledAgents arrays are required.' }, 400);
2134
+ }
2135
+ if (!Number.isInteger(body.expectedRevision) || body.expectedRevision < 0) {
2136
+ return c.json({ error: 'A nonnegative integer expectedRevision is required.' }, 400);
2137
+ }
2138
+ const config = await loadConfig();
2139
+ const wsPath = await resolveExactWorkspaceById(config.workspacesDir, id);
2140
+ if (!wsPath)
2141
+ return c.json({ error: 'Workspace not found.' }, 404);
2142
+ const saved = await withResourceAdministrationLock(async () => {
2143
+ await validateResourceSelections(body.enabledSkills, body.enabledAgents);
2144
+ return saveWorkspaceSkillsConfig(wsPath, {
2145
+ enabledSkills: body.enabledSkills,
2146
+ enabledAgents: body.enabledAgents,
2147
+ enabledCategories: Array.isArray(body.enabledCategories) ? body.enabledCategories : [],
2148
+ }, body.expectedRevision);
2149
+ });
2150
+ return c.json({ success: true, config: saved });
2151
+ }
2152
+ catch (error) {
2153
+ return errorResponse(c, error);
2154
+ }
2155
+ });
1850
2156
  // ─── Schedules: recurring workspace jobs (sync/refresh) ─────────────────
1851
2157
  // List all scheduled jobs (with computed next-due time)
1852
2158
  app.get('/api/schedules', async (c) => {