@pi-harness/pi-harness 0.1.1

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 (185) hide show
  1. package/README.md +161 -0
  2. package/apps/web/dist/assets/index-C4tLweAB.js +20 -0
  3. package/apps/web/dist/assets/index-DPgRbQ4_.css +1 -0
  4. package/apps/web/dist/index.html +14 -0
  5. package/apps/web/index.html +13 -0
  6. package/apps/web/package.json +24 -0
  7. package/apps/web/profile/cordis.yml +44 -0
  8. package/apps/web/server-dist/bin.d.ts +3 -0
  9. package/apps/web/server-dist/bin.d.ts.map +1 -0
  10. package/apps/web/server-dist/bin.js +67 -0
  11. package/apps/web/server-dist/bin.js.map +1 -0
  12. package/apps/web/src/command-palette.ts +84 -0
  13. package/apps/web/src/file-diff.ts +42 -0
  14. package/apps/web/src/main.ts +12 -0
  15. package/apps/web/src/runtime.css +4 -0
  16. package/apps/web/src/server/bin.ts +64 -0
  17. package/apps/web/src/style.css +7 -0
  18. package/apps/web/tsconfig.build.json +1 -0
  19. package/apps/web/tsconfig.json +1 -0
  20. package/package.json +48 -0
  21. package/packages/api-gateway/dist/index.d.ts +8 -0
  22. package/packages/api-gateway/dist/index.d.ts.map +1 -0
  23. package/packages/api-gateway/dist/index.js +582 -0
  24. package/packages/api-gateway/dist/index.js.map +1 -0
  25. package/packages/api-gateway/package.json +29 -0
  26. package/packages/api-gateway/src/index.ts +577 -0
  27. package/packages/api-gateway/test/index.test.ts +327 -0
  28. package/packages/api-gateway/tsconfig.build.json +1 -0
  29. package/packages/api-gateway/tsconfig.json +1 -0
  30. package/packages/bundle-web-app/cordis.patch.yml +12 -0
  31. package/packages/bundle-web-app/dist/index.d.ts +12 -0
  32. package/packages/bundle-web-app/dist/index.d.ts.map +1 -0
  33. package/packages/bundle-web-app/dist/index.js +63 -0
  34. package/packages/bundle-web-app/dist/index.js.map +1 -0
  35. package/packages/bundle-web-app/package.json +30 -0
  36. package/packages/bundle-web-app/src/index.ts +69 -0
  37. package/packages/bundle-web-app/test/index.test.ts +29 -0
  38. package/packages/bundle-web-app/tsconfig.build.json +1 -0
  39. package/packages/bundle-web-app/tsconfig.json +1 -0
  40. package/packages/cli/dist/args.d.ts +16 -0
  41. package/packages/cli/dist/args.d.ts.map +1 -0
  42. package/packages/cli/dist/args.js +53 -0
  43. package/packages/cli/dist/args.js.map +1 -0
  44. package/packages/cli/dist/bin.d.ts +3 -0
  45. package/packages/cli/dist/bin.d.ts.map +1 -0
  46. package/packages/cli/dist/bin.js +40 -0
  47. package/packages/cli/dist/bin.js.map +1 -0
  48. package/packages/cli/dist/main.d.ts +14 -0
  49. package/packages/cli/dist/main.d.ts.map +1 -0
  50. package/packages/cli/dist/main.js +148 -0
  51. package/packages/cli/dist/main.js.map +1 -0
  52. package/packages/cli/dist/node-stdio.d.ts +10 -0
  53. package/packages/cli/dist/node-stdio.d.ts.map +1 -0
  54. package/packages/cli/dist/node-stdio.js +34 -0
  55. package/packages/cli/dist/node-stdio.js.map +1 -0
  56. package/packages/cli/dist/relaunch.d.ts +5 -0
  57. package/packages/cli/dist/relaunch.d.ts.map +1 -0
  58. package/packages/cli/dist/relaunch.js +52 -0
  59. package/packages/cli/dist/relaunch.js.map +1 -0
  60. package/packages/cli/package.json +25 -0
  61. package/packages/cli/src/args.ts +50 -0
  62. package/packages/cli/src/bin.ts +41 -0
  63. package/packages/cli/src/main.ts +155 -0
  64. package/packages/cli/src/node-stdio.ts +40 -0
  65. package/packages/cli/src/relaunch.ts +48 -0
  66. package/packages/cli/test/args.test.ts +28 -0
  67. package/packages/cli/test/cli.test.ts +153 -0
  68. package/packages/cli/test/relaunch.test.ts +29 -0
  69. package/packages/cli/tsconfig.build.json +11 -0
  70. package/packages/cli/tsconfig.json +10 -0
  71. package/packages/client-web/dist/control-room.d.ts +102 -0
  72. package/packages/client-web/dist/control-room.d.ts.map +1 -0
  73. package/packages/client-web/dist/control-room.js +12 -0
  74. package/packages/client-web/dist/control-room.js.map +1 -0
  75. package/packages/client-web/dist/design-contract.d.ts +85 -0
  76. package/packages/client-web/dist/design-contract.d.ts.map +1 -0
  77. package/packages/client-web/dist/design-contract.js +99 -0
  78. package/packages/client-web/dist/design-contract.js.map +1 -0
  79. package/packages/client-web/dist/index.d.ts +17 -0
  80. package/packages/client-web/dist/index.d.ts.map +1 -0
  81. package/packages/client-web/dist/index.js +19 -0
  82. package/packages/client-web/dist/index.js.map +1 -0
  83. package/packages/client-web/dist/react-room.d.ts +6 -0
  84. package/packages/client-web/dist/react-room.d.ts.map +1 -0
  85. package/packages/client-web/dist/react-room.js +103 -0
  86. package/packages/client-web/dist/react-room.js.map +1 -0
  87. package/packages/client-web/package.json +29 -0
  88. package/packages/client-web/src/control-room.ts +10 -0
  89. package/packages/client-web/src/design-contract.ts +112 -0
  90. package/packages/client-web/src/index.ts +21 -0
  91. package/packages/client-web/src/react-room.tsx +78 -0
  92. package/packages/client-web/test/design-contract.test.ts +24 -0
  93. package/packages/client-web/tsconfig.build.json +1 -0
  94. package/packages/client-web/tsconfig.json +1 -0
  95. package/packages/core/dist/boot.d.ts +13 -0
  96. package/packages/core/dist/boot.d.ts.map +1 -0
  97. package/packages/core/dist/boot.js +96 -0
  98. package/packages/core/dist/boot.js.map +1 -0
  99. package/packages/core/dist/context.d.ts +3 -0
  100. package/packages/core/dist/context.d.ts.map +1 -0
  101. package/packages/core/dist/context.js +2 -0
  102. package/packages/core/dist/context.js.map +1 -0
  103. package/packages/core/dist/index.d.ts +7 -0
  104. package/packages/core/dist/index.d.ts.map +1 -0
  105. package/packages/core/dist/index.js +6 -0
  106. package/packages/core/dist/index.js.map +1 -0
  107. package/packages/core/dist/plugins/model.d.ts +8 -0
  108. package/packages/core/dist/plugins/model.d.ts.map +1 -0
  109. package/packages/core/dist/plugins/model.js +12 -0
  110. package/packages/core/dist/plugins/model.js.map +1 -0
  111. package/packages/core/dist/plugins/models.d.ts +16 -0
  112. package/packages/core/dist/plugins/models.d.ts.map +1 -0
  113. package/packages/core/dist/plugins/models.js +24 -0
  114. package/packages/core/dist/plugins/models.js.map +1 -0
  115. package/packages/core/dist/plugins/resources.d.ts +18 -0
  116. package/packages/core/dist/plugins/resources.d.ts.map +1 -0
  117. package/packages/core/dist/plugins/resources.js +48 -0
  118. package/packages/core/dist/plugins/resources.js.map +1 -0
  119. package/packages/core/dist/plugins/runtime.d.ts +14 -0
  120. package/packages/core/dist/plugins/runtime.d.ts.map +1 -0
  121. package/packages/core/dist/plugins/runtime.js +68 -0
  122. package/packages/core/dist/plugins/runtime.js.map +1 -0
  123. package/packages/core/dist/plugins/session.d.ts +15 -0
  124. package/packages/core/dist/plugins/session.d.ts.map +1 -0
  125. package/packages/core/dist/plugins/session.js +20 -0
  126. package/packages/core/dist/plugins/session.js.map +1 -0
  127. package/packages/core/dist/plugins/stdio.d.ts +8 -0
  128. package/packages/core/dist/plugins/stdio.d.ts.map +1 -0
  129. package/packages/core/dist/plugins/stdio.js +20 -0
  130. package/packages/core/dist/plugins/stdio.js.map +1 -0
  131. package/packages/core/dist/plugins/tools.d.ts +13 -0
  132. package/packages/core/dist/plugins/tools.d.ts.map +1 -0
  133. package/packages/core/dist/plugins/tools.js +17 -0
  134. package/packages/core/dist/plugins/tools.js.map +1 -0
  135. package/packages/core/dist/profile.d.ts +8 -0
  136. package/packages/core/dist/profile.d.ts.map +1 -0
  137. package/packages/core/dist/profile.js +28 -0
  138. package/packages/core/dist/profile.js.map +1 -0
  139. package/packages/core/dist/runtime.d.ts +16 -0
  140. package/packages/core/dist/runtime.d.ts.map +1 -0
  141. package/packages/core/dist/runtime.js +33 -0
  142. package/packages/core/dist/runtime.js.map +1 -0
  143. package/packages/core/dist/services.d.ts +63 -0
  144. package/packages/core/dist/services.d.ts.map +1 -0
  145. package/packages/core/dist/services.js +39 -0
  146. package/packages/core/dist/services.js.map +1 -0
  147. package/packages/core/dist/stdio.d.ts +25 -0
  148. package/packages/core/dist/stdio.d.ts.map +1 -0
  149. package/packages/core/dist/stdio.js +74 -0
  150. package/packages/core/dist/stdio.js.map +1 -0
  151. package/packages/core/package.json +44 -0
  152. package/packages/core/profiles/default/cordis.yml +35 -0
  153. package/packages/core/profiles/development/cordis.yml +47 -0
  154. package/packages/core/src/boot.ts +103 -0
  155. package/packages/core/src/context.ts +3 -0
  156. package/packages/core/src/index.ts +6 -0
  157. package/packages/core/src/plugins/model.ts +12 -0
  158. package/packages/core/src/plugins/models.ts +32 -0
  159. package/packages/core/src/plugins/resources.ts +57 -0
  160. package/packages/core/src/plugins/runtime.ts +73 -0
  161. package/packages/core/src/plugins/session.ts +27 -0
  162. package/packages/core/src/plugins/stdio.ts +20 -0
  163. package/packages/core/src/plugins/tools.ts +23 -0
  164. package/packages/core/src/profile.ts +31 -0
  165. package/packages/core/src/runtime.ts +39 -0
  166. package/packages/core/src/services.ts +105 -0
  167. package/packages/core/src/stdio.ts +88 -0
  168. package/packages/core/test/boot.test.ts +86 -0
  169. package/packages/core/test/profile.test.ts +39 -0
  170. package/packages/core/test/profiles.integration.test.ts +93 -0
  171. package/packages/core/test/runtime-fixture.ts +31 -0
  172. package/packages/core/test/runtime.test.ts +54 -0
  173. package/packages/core/test/services.test.ts +131 -0
  174. package/packages/core/test/stdio.test.ts +87 -0
  175. package/packages/core/tsconfig.build.json +10 -0
  176. package/packages/core/tsconfig.json +10 -0
  177. package/packages/host-webserver/dist/index.d.ts +29 -0
  178. package/packages/host-webserver/dist/index.d.ts.map +1 -0
  179. package/packages/host-webserver/dist/index.js +73 -0
  180. package/packages/host-webserver/dist/index.js.map +1 -0
  181. package/packages/host-webserver/package.json +27 -0
  182. package/packages/host-webserver/src/index.ts +97 -0
  183. package/packages/host-webserver/test/index.test.ts +43 -0
  184. package/packages/host-webserver/tsconfig.build.json +1 -0
  185. package/packages/host-webserver/tsconfig.json +1 -0
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@pi-harness/core",
3
+ "version": "0.1.1",
4
+ "description": "Cordis boot and Pi runtime plugins for Pi Harness",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./plugins/*": {
14
+ "types": "./dist/plugins/*.d.ts",
15
+ "import": "./dist/plugins/*.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "profiles"
22
+ ],
23
+ "engines": {
24
+ "node": ">=22.19.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc -p tsconfig.build.json",
28
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
29
+ "typecheck": "tsc -p tsconfig.json"
30
+ },
31
+ "dependencies": {
32
+ "@deepseek-ai/cordis": "4.0.1",
33
+ "@deepseek-ai/cordis-plugin-group": "1.0.1",
34
+ "@deepseek-ai/cordis-plugin-hmr": "1.0.16",
35
+ "@deepseek-ai/cordis-plugin-include": "1.0.6",
36
+ "@deepseek-ai/cordis-plugin-loader": "1.0.2",
37
+ "@deepseek-ai/cordis-plugin-logger-console": "1.0.1",
38
+ "@deepseek-ai/cordis-plugin-timer": "1.1.3",
39
+ "@deepseek-ai/schemastery": "3.18.1",
40
+ "@earendil-works/pi-agent-core": "0.84.4",
41
+ "@earendil-works/pi-ai": "0.84.4",
42
+ "@earendil-works/pi-coding-agent": "0.84.4"
43
+ }
44
+ }
@@ -0,0 +1,35 @@
1
+ - id: agent
2
+ name: cordis:group
3
+ group: true
4
+ config:
5
+ - id: models
6
+ name: "@pi-harness/core/plugins/models"
7
+ config:
8
+ provider: deepseek
9
+ model: deepseek-v4-flash
10
+ refreshOnCreate: false
11
+ - id: resources
12
+ name: "@pi-harness/core/plugins/resources"
13
+ config: {}
14
+ - id: model
15
+ name: "@pi-harness/core/plugins/model"
16
+ config: {}
17
+ - id: session
18
+ name: "@pi-harness/core/plugins/session"
19
+ config:
20
+ storage: jsonl
21
+ - id: tools
22
+ name: "@pi-harness/core/plugins/tools"
23
+ config:
24
+ names:
25
+ - read
26
+ - bash
27
+ - edit
28
+ - write
29
+ - id: runtime
30
+ name: "@pi-harness/core/plugins/runtime"
31
+ config:
32
+ thinkingLevel: medium
33
+ - id: stdio
34
+ name: "@pi-harness/core/plugins/stdio"
35
+ config: {}
@@ -0,0 +1,47 @@
1
+ - id: logger
2
+ name: "@deepseek-ai/cordis-plugin-logger-console"
3
+ config: {}
4
+ - id: timer
5
+ name: "@deepseek-ai/cordis-plugin-timer"
6
+ config: {}
7
+ - id: hmr
8
+ name: "@deepseek-ai/cordis-plugin-hmr"
9
+ config:
10
+ base: !!js piHarnessLaunch.cwd
11
+ root:
12
+ - .
13
+ - id: agent
14
+ name: cordis:group
15
+ group: true
16
+ config:
17
+ - id: models
18
+ name: "@pi-harness/core/plugins/models"
19
+ config:
20
+ provider: deepseek
21
+ model: deepseek-v4-flash
22
+ refreshOnCreate: false
23
+ - id: resources
24
+ name: "@pi-harness/core/plugins/resources"
25
+ config: {}
26
+ - id: model
27
+ name: "@pi-harness/core/plugins/model"
28
+ config: {}
29
+ - id: session
30
+ name: "@pi-harness/core/plugins/session"
31
+ config:
32
+ storage: jsonl
33
+ - id: tools
34
+ name: "@pi-harness/core/plugins/tools"
35
+ config:
36
+ names:
37
+ - read
38
+ - bash
39
+ - edit
40
+ - write
41
+ - id: runtime
42
+ name: "@pi-harness/core/plugins/runtime"
43
+ config:
44
+ thinkingLevel: medium
45
+ - id: stdio
46
+ name: "@pi-harness/core/plugins/stdio"
47
+ config: {}
@@ -0,0 +1,103 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ import { Context, type FiberState } from "@deepseek-ai/cordis";
4
+ import Group from "@deepseek-ai/cordis-plugin-group";
5
+ import Include from "@deepseek-ai/cordis-plugin-include";
6
+ import Loader, { type EntryOptions } from "@deepseek-ai/cordis-plugin-loader";
7
+
8
+ class ReadonlyInclude extends Include {
9
+ override write(): void {}
10
+ }
11
+
12
+ export interface BootHarnessOptions {
13
+ configPath: string;
14
+ prepare?: (context: Context) => Promise<void> | void;
15
+ onFullReload?: () => void;
16
+ signal?: AbortSignal;
17
+ }
18
+
19
+ export interface BootedHarness {
20
+ readonly context: Context;
21
+ dispose(): Promise<void>;
22
+ }
23
+
24
+ const FIBER_PENDING = 0 as FiberState.PENDING;
25
+ const FIBER_ACTIVE = 2 as FiberState.ACTIVE;
26
+ const FIBER_FAILED = 3 as FiberState.FAILED;
27
+
28
+ function formatError(error: unknown): string {
29
+ return error instanceof Error ? error.stack ?? error.message : String(error);
30
+ }
31
+
32
+ async function assertEntriesActivated(context: Context): Promise<void> {
33
+ const loader = context.get("loader");
34
+ if (loader === undefined) throw new Error("Cordis Loader was disposed during startup");
35
+ const failures: string[] = [];
36
+ for (const entry of loader.entries()) {
37
+ if (entry.disabled) continue;
38
+ const fiber = entry.fiber;
39
+ if (fiber === undefined) {
40
+ failures.push(`${entry.options.name}: module did not load`);
41
+ continue;
42
+ }
43
+ if (fiber.state === FIBER_ACTIVE) continue;
44
+ if (fiber.state === FIBER_FAILED) {
45
+ try {
46
+ await fiber.await();
47
+ } catch (error) {
48
+ failures.push(`${entry.options.name}: ${formatError(error)}`);
49
+ }
50
+ continue;
51
+ }
52
+ if (fiber.state === FIBER_PENDING) {
53
+ const missing = Object.keys(fiber.inject).filter((service) => fiber.ctx.get(service) === undefined);
54
+ failures.push(`${entry.options.name}: pending (waiting for services: ${missing.join(", ") || "unknown"})`);
55
+ continue;
56
+ }
57
+ failures.push(`${entry.options.name}: fiber state ${String(fiber.state)}`);
58
+ }
59
+ if (failures.length > 0) throw new Error(`Cordis plugin tree did not activate:\n${failures.join("\n")}`);
60
+ }
61
+
62
+ async function mountProfile(context: Context, configPath: string): Promise<void> {
63
+ context.loader.builtins.include = ReadonlyInclude;
64
+ context.loader.builtins.group = Group;
65
+ const root: EntryOptions = {
66
+ id: "profile",
67
+ name: "cordis:include",
68
+ config: { path: pathToFileURL(configPath).href },
69
+ };
70
+ await context.loader.create(root);
71
+ }
72
+
73
+ export async function bootHarness(options: BootHarnessOptions): Promise<BootedHarness> {
74
+ const configPath = resolve(options.configPath);
75
+ const context = new Context();
76
+ const abort = () => {
77
+ void context.fiber.dispose().catch(() => {});
78
+ };
79
+ if (options.signal?.aborted) throw new Error("Pi Harness startup was aborted", { cause: options.signal.reason });
80
+ options.signal?.addEventListener("abort", abort, { once: true });
81
+ let stage = "host preparation";
82
+ try {
83
+ context.baseUrl = `${pathToFileURL(dirname(configPath)).href}/`;
84
+ await context.plugin(Loader);
85
+ if (options.onFullReload !== undefined) context.loader.exit = options.onFullReload;
86
+ await options.prepare?.(context);
87
+ stage = "plugin tree activation";
88
+ await mountProfile(context, configPath);
89
+ await context.get("loader")?.await();
90
+ if (context.get("loader") !== undefined) await assertEntriesActivated(context);
91
+ } catch (cause) {
92
+ await context.fiber.dispose();
93
+ throw new Error(`Pi Harness ${stage} failed: ${formatError(cause)}`, { cause });
94
+ } finally {
95
+ options.signal?.removeEventListener("abort", abort);
96
+ }
97
+ return {
98
+ context,
99
+ async dispose() {
100
+ await context.fiber.dispose();
101
+ },
102
+ };
103
+ }
@@ -0,0 +1,3 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+
3
+ export type HarnessContext = Context;
@@ -0,0 +1,6 @@
1
+ export * from "./boot.js";
2
+ export type * from "./context.js";
3
+ export * from "./profile.js";
4
+ export * from "./runtime.js";
5
+ export * from "./services.js";
6
+ export * from "./stdio.js";
@@ -0,0 +1,12 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+
3
+ export default {
4
+ name: "pi-model",
5
+ inject: ["piModelRuntime", "piResources"],
6
+ apply(context: Context) {
7
+ const { runtime, provider, model: modelId } = context.piModelRuntime;
8
+ const model = runtime.getModel(provider, modelId);
9
+ if (model === undefined) throw new Error(`Pi model is not registered: ${provider}/${modelId}`);
10
+ context.provide("piModels", { runtime, model });
11
+ },
12
+ };
@@ -0,0 +1,32 @@
1
+ import { join } from "node:path";
2
+ import type { Context } from "@deepseek-ai/cordis";
3
+ import z from "@deepseek-ai/schemastery";
4
+ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
5
+
6
+ export interface ModelsPluginConfig {
7
+ provider: string;
8
+ model: string;
9
+ refreshOnCreate?: boolean;
10
+ }
11
+
12
+ export const Config: z<ModelsPluginConfig> = z.object({
13
+ provider: z.string().required(),
14
+ model: z.string().required(),
15
+ refreshOnCreate: z.boolean().default(false),
16
+ });
17
+
18
+ export default {
19
+ name: "pi-models",
20
+ inject: ["piHarnessLaunch"],
21
+ Config,
22
+ async apply(context: Context, config: ModelsPluginConfig) {
23
+ const agentDir = context.piHarnessLaunch.agentDir;
24
+ const runtime = await ModelRuntime.create({
25
+ authPath: join(agentDir, "auth.json"),
26
+ modelsPath: join(agentDir, "models.json"),
27
+ modelsStorePath: join(agentDir, "models-store.json"),
28
+ refreshOnCreate: config.refreshOnCreate ?? false,
29
+ });
30
+ context.provide("piModelRuntime", { runtime, provider: config.provider, model: config.model });
31
+ },
32
+ };
@@ -0,0 +1,57 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { createAgentSessionServices } from "@earendil-works/pi-coding-agent";
4
+
5
+ export interface ResourcesPluginConfig {
6
+ noExtensions?: boolean;
7
+ noSkills?: boolean;
8
+ noPromptTemplates?: boolean;
9
+ noThemes?: boolean;
10
+ noContextFiles?: boolean;
11
+ }
12
+
13
+ export const Config: z<ResourcesPluginConfig> = z.object({
14
+ noExtensions: z.boolean().default(false),
15
+ noSkills: z.boolean().default(false),
16
+ noPromptTemplates: z.boolean().default(false),
17
+ noThemes: z.boolean().default(false),
18
+ noContextFiles: z.boolean().default(false),
19
+ });
20
+
21
+ export default {
22
+ name: "pi-resources",
23
+ inject: ["piHarnessLaunch", "piModelRuntime"],
24
+ Config,
25
+ async apply(context: Context, config: ResourcesPluginConfig) {
26
+ const services = await createAgentSessionServices({
27
+ cwd: context.piHarnessLaunch.cwd,
28
+ agentDir: context.piHarnessLaunch.agentDir,
29
+ modelRuntime: context.piModelRuntime.runtime,
30
+ resourceLoaderOptions: {
31
+ noExtensions: config.noExtensions ?? false,
32
+ noSkills: config.noSkills ?? false,
33
+ noPromptTemplates: config.noPromptTemplates ?? false,
34
+ noThemes: config.noThemes ?? false,
35
+ noContextFiles: config.noContextFiles ?? false,
36
+ },
37
+ });
38
+ const errors = services.diagnostics.filter((diagnostic) => diagnostic.type === "error");
39
+ if (errors.length > 0) throw new Error(`Pi resource loading failed:\n${errors.map((diagnostic) => diagnostic.message).join("\n")}`);
40
+ const resourceLoaderOptions = {
41
+ noExtensions: config.noExtensions ?? false,
42
+ noSkills: config.noSkills ?? false,
43
+ noPromptTemplates: config.noPromptTemplates ?? false,
44
+ noThemes: config.noThemes ?? false,
45
+ noContextFiles: config.noContextFiles ?? false,
46
+ };
47
+ context.provide("piResources", {
48
+ ...services,
49
+ createForCwd: (cwd: string) => createAgentSessionServices({
50
+ cwd,
51
+ agentDir: context.piHarnessLaunch.agentDir,
52
+ modelRuntime: context.piModelRuntime.runtime,
53
+ resourceLoaderOptions,
54
+ }),
55
+ });
56
+ },
57
+ };
@@ -0,0 +1,73 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { createAgentSessionFromServices, createAgentSessionRuntime, type AgentSession, type CreateAgentSessionRuntimeFactory } from "@earendil-works/pi-coding-agent";
4
+ import { PiRuntime } from "../runtime.js";
5
+
6
+ export interface RuntimePluginConfig {
7
+ thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
8
+ }
9
+
10
+ export const Config: z<RuntimePluginConfig> = z.object({
11
+ thinkingLevel: z.union(["off", "minimal", "low", "medium", "high", "xhigh", "max"]).default("medium"),
12
+ });
13
+
14
+ export default {
15
+ name: "pi-runtime",
16
+ inject: ["piModels", "piResources", "piSession", "piTools"],
17
+ Config,
18
+ async apply(context: Context, config: RuntimePluginConfig) {
19
+ const tools = context.piTools.acquire();
20
+ context.effect(() => () => tools.release());
21
+ const requestedTools = [...tools.names, ...tools.customTools.map((tool) => tool.name)];
22
+ const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
23
+ const services = cwd === context.piResources.cwd ? context.piResources : await context.piResources.createForCwd(cwd);
24
+ const result = await createAgentSessionFromServices({
25
+ services,
26
+ sessionManager,
27
+ ...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
28
+ model: context.piModels.model,
29
+ thinkingLevel: config.thinkingLevel ?? "medium",
30
+ tools: requestedTools,
31
+ customTools: tools.customTools,
32
+ });
33
+ return { ...result, services, diagnostics: services.diagnostics };
34
+ };
35
+ const sessionRuntime = await createAgentSessionRuntime(createRuntime, {
36
+ cwd: context.piResources.cwd,
37
+ agentDir: context.piResources.agentDir,
38
+ sessionManager: context.piSession.manager,
39
+ });
40
+ const runtime = new PiRuntime(sessionRuntime);
41
+ const bindSession = async (session: AgentSession): Promise<void> => {
42
+ const activeTools = new Set(session.getAllTools().map((tool) => tool.name));
43
+ const missingTools = requestedTools.filter((name) => !activeTools.has(name));
44
+ if (missingTools.length > 0) throw new Error(`Pi tools are not registered: ${missingTools.join(", ")}`);
45
+ await session.bindExtensions({
46
+ mode: "print",
47
+ abortHandler: () => {
48
+ void runtime.abort();
49
+ },
50
+ onError: (error) => {
51
+ context.emit("pi/extension-error", error);
52
+ },
53
+ });
54
+ };
55
+ context.provide("piRuntime", runtime);
56
+ let unsubscribe: (() => void) | undefined;
57
+ const rebindSession = async (session: AgentSession): Promise<void> => {
58
+ unsubscribe?.();
59
+ await bindSession(session);
60
+ unsubscribe = session.subscribe((event) => {
61
+ context.emit("pi/session-event", event);
62
+ });
63
+ };
64
+ sessionRuntime.setRebindSession(rebindSession);
65
+ await rebindSession(sessionRuntime.session);
66
+ context.effect(() => {
67
+ return async () => {
68
+ unsubscribe?.();
69
+ await runtime.dispose();
70
+ };
71
+ });
72
+ },
73
+ };
@@ -0,0 +1,27 @@
1
+ import { join, resolve } from "node:path";
2
+ import type { Context } from "@deepseek-ai/cordis";
3
+ import z from "@deepseek-ai/schemastery";
4
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
5
+
6
+ export interface SessionPluginConfig {
7
+ storage?: "memory" | "jsonl";
8
+ directory?: string;
9
+ }
10
+
11
+ export const Config: z<SessionPluginConfig> = z.object({
12
+ storage: z.union(["memory", "jsonl"]).default("jsonl"),
13
+ directory: z.string(),
14
+ });
15
+
16
+ export default {
17
+ name: "pi-session",
18
+ inject: ["piHarnessLaunch"],
19
+ Config,
20
+ apply(context: Context, config: SessionPluginConfig) {
21
+ const storage = config.storage ?? "jsonl";
22
+ const manager = storage === "memory"
23
+ ? SessionManager.inMemory(context.piHarnessLaunch.cwd)
24
+ : SessionManager.create(context.piHarnessLaunch.cwd, config.directory === undefined ? join(context.piHarnessLaunch.agentDir, "sessions") : resolve(context.piHarnessLaunch.cwd, config.directory));
25
+ context.provide("piSession", { manager });
26
+ },
27
+ };
@@ -0,0 +1,20 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import { StdioApplication } from "../stdio.js";
3
+
4
+ export default {
5
+ name: "pi-stdio",
6
+ inject: ["piRuntime", "piResources", "piHarnessStdio", "piHarnessLaunch"],
7
+ apply(context: Context) {
8
+ const application = new StdioApplication(context.piRuntime, context.piHarnessLaunch, context.piHarnessStdio);
9
+ for (const diagnostic of context.piResources.diagnostics) {
10
+ if (diagnostic.type !== "error") context.piHarnessStdio.writeError(`Resource ${diagnostic.type}: ${diagnostic.message}\n`);
11
+ }
12
+ context.on("pi/session-event", (event) => {
13
+ application.writeSessionEvent(event);
14
+ });
15
+ context.on("pi/extension-error", (error) => {
16
+ context.piHarnessStdio.writeError(`Extension error (${error.extensionPath}): ${error.error}\n`);
17
+ });
18
+ context.provide("piApplication", application);
19
+ },
20
+ };
@@ -0,0 +1,23 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { PiToolRegistry } from "../services.js";
4
+
5
+ export interface ToolsPluginConfig {
6
+ names?: string[];
7
+ }
8
+
9
+ const DEFAULT_TOOLS = ["read", "bash", "edit", "write"];
10
+
11
+ export const Config: z<ToolsPluginConfig> = z.object({
12
+ names: z.array(z.string()).default(DEFAULT_TOOLS),
13
+ });
14
+
15
+ export default {
16
+ name: "pi-tools",
17
+ Config,
18
+ apply(context: Context, config: ToolsPluginConfig) {
19
+ const names = config.names ?? DEFAULT_TOOLS;
20
+ if (new Set(names).size !== names.length) throw new Error("Pi core tool names must be unique");
21
+ context.provide("piTools", new PiToolRegistry(names));
22
+ },
23
+ };
@@ -0,0 +1,31 @@
1
+ import { access } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ export interface ResolveProfileConfigOptions {
6
+ profile?: string;
7
+ configPath?: string;
8
+ cwd?: string;
9
+ profilesDir?: string;
10
+ }
11
+
12
+ export async function resolveProfileConfig(_options: ResolveProfileConfigOptions = {}): Promise<string> {
13
+ const options = _options;
14
+ if (options.profile !== undefined && options.configPath !== undefined) throw new Error("profile and configPath cannot be used together");
15
+ const cwd = options.cwd ?? process.cwd();
16
+ let configPath: string;
17
+ if (options.configPath !== undefined) {
18
+ configPath = resolve(cwd, options.configPath);
19
+ } else {
20
+ const profile = options.profile ?? "default";
21
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(profile)) throw new Error(`Invalid profile name: ${profile}`);
22
+ const profilesDir = options.profilesDir ?? fileURLToPath(new URL("../profiles", import.meta.url));
23
+ configPath = resolve(profilesDir, profile, "cordis.yml");
24
+ }
25
+ try {
26
+ await access(configPath);
27
+ } catch (cause) {
28
+ throw new Error(`Pi Harness profile config does not exist or is not readable: ${configPath}`, { cause });
29
+ }
30
+ return configPath;
31
+ }
@@ -0,0 +1,39 @@
1
+ import type { AgentSession, AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
2
+ import type { PiRuntimeService } from "./services.js";
3
+
4
+ export class PiRuntimeDisposedError extends Error {
5
+ override readonly name = "PiRuntimeDisposedError";
6
+
7
+ constructor() {
8
+ super("Pi runtime is disposed");
9
+ }
10
+ }
11
+
12
+ export class PiRuntime implements PiRuntimeService {
13
+ readonly sessionRuntime: AgentSessionRuntime;
14
+ #disposed = false;
15
+
16
+ constructor(sessionRuntime: AgentSessionRuntime) {
17
+ this.sessionRuntime = sessionRuntime;
18
+ }
19
+
20
+ get session(): AgentSession {
21
+ return this.sessionRuntime.session;
22
+ }
23
+
24
+ async prompt(text: string): Promise<void> {
25
+ if (this.#disposed) throw new PiRuntimeDisposedError();
26
+ await this.sessionRuntime.session.prompt(text);
27
+ }
28
+
29
+ async abort(): Promise<void> {
30
+ if (this.#disposed) return;
31
+ await this.sessionRuntime.session.abort();
32
+ }
33
+
34
+ async dispose(): Promise<void> {
35
+ if (this.#disposed) return;
36
+ this.#disposed = true;
37
+ await this.sessionRuntime.dispose();
38
+ }
39
+ }
@@ -0,0 +1,105 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+ import type { AgentSession, AgentSessionEvent, AgentSessionRuntime, AgentSessionServices, ExtensionError, SessionManager, ToolDefinition } from "@earendil-works/pi-coding-agent";
3
+ import type { Api, Model } from "@earendil-works/pi-ai";
4
+ import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
5
+
6
+ export interface PiHarnessLaunch {
7
+ readonly cwd: string;
8
+ readonly agentDir: string;
9
+ readonly args: readonly string[];
10
+ requestExit(code: number): void;
11
+ }
12
+
13
+ export interface PiModelsService {
14
+ readonly runtime: ModelRuntime;
15
+ readonly model: Model<Api>;
16
+ }
17
+
18
+ export interface PiModelRuntimeService {
19
+ readonly runtime: ModelRuntime;
20
+ readonly provider: string;
21
+ readonly model: string;
22
+ }
23
+
24
+ export type PiResourcesService = AgentSessionServices & {
25
+ createForCwd(cwd: string): Promise<AgentSessionServices>;
26
+ };
27
+
28
+ export interface PiSessionService {
29
+ readonly manager: SessionManager;
30
+ }
31
+
32
+ export interface PiRuntimeService {
33
+ readonly session: AgentSession;
34
+ readonly sessionRuntime: AgentSessionRuntime;
35
+ prompt(text: string): Promise<void>;
36
+ abort(): Promise<void>;
37
+ dispose(): Promise<void>;
38
+ }
39
+
40
+ export interface PiToolsSnapshot {
41
+ readonly names: string[];
42
+ readonly customTools: ToolDefinition[];
43
+ }
44
+
45
+ export interface PiToolsLease extends PiToolsSnapshot {
46
+ release(): void;
47
+ }
48
+
49
+ export class PiToolRegistry {
50
+ readonly #names: string[];
51
+ readonly #customTools = new Map<string, ToolDefinition>();
52
+ #leases = 0;
53
+
54
+ constructor(names: readonly string[] = []) {
55
+ this.#names = [...names];
56
+ }
57
+
58
+ register(tool: ToolDefinition): () => void {
59
+ if (this.#leases > 0) throw new Error(`Pi tool registry is leased by pi-runtime; declare a Cordis injection that activates ${tool.name} before pi-runtime`);
60
+ if (this.#names.includes(tool.name) || this.#customTools.has(tool.name)) throw new Error(`Pi tool is already registered: ${tool.name}`);
61
+ this.#customTools.set(tool.name, tool);
62
+ return () => {
63
+ this.#customTools.delete(tool.name);
64
+ };
65
+ }
66
+
67
+ snapshot(): PiToolsSnapshot {
68
+ return { names: [...this.#names], customTools: [...this.#customTools.values()] };
69
+ }
70
+
71
+ acquire(): PiToolsLease {
72
+ this.#leases += 1;
73
+ let released = false;
74
+ return {
75
+ ...this.snapshot(),
76
+ release: () => {
77
+ if (released) return;
78
+ released = true;
79
+ this.#leases -= 1;
80
+ },
81
+ };
82
+ }
83
+ }
84
+
85
+ declare module "@deepseek-ai/cordis" {
86
+ interface Context {
87
+ piHarnessLaunch: PiHarnessLaunch;
88
+ piModelRuntime: PiModelRuntimeService;
89
+ piModels: PiModelsService;
90
+ piResources: PiResourcesService;
91
+ piSession: PiSessionService;
92
+ piTools: PiToolRegistry;
93
+ piRuntime: PiRuntimeService;
94
+ }
95
+
96
+ interface Events {
97
+ "pi/session-event"(event: AgentSessionEvent): void;
98
+ "pi/extension-error"(error: ExtensionError): void;
99
+ }
100
+ }
101
+
102
+ export function provideLaunchContext(context: Context, launch: PiHarnessLaunch): () => void {
103
+ const value: PiHarnessLaunch = Object.freeze({ ...launch, args: Object.freeze([...launch.args]) });
104
+ return context.provide("piHarnessLaunch", value);
105
+ }