pi-harness-runtime 1.1.393 → 1.1.395

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 (221) hide show
  1. package/AGENTS.md +125 -3
  2. package/harness/console-guard.ts +73 -0
  3. package/harness/glm-quota-logger.ts +17 -0
  4. package/harness/langchain/planner-logger.ts +15 -0
  5. package/harness/service-diagnostics.ts +32 -1
  6. package/index.ts +195 -4
  7. package/package.json +1 -1
  8. package/packages/a2a-adapter/package.json +1 -1
  9. package/packages/agent-policy/package.json +1 -1
  10. package/packages/architecture-generator/package.json +1 -1
  11. package/packages/auth/package.json +1 -1
  12. package/packages/autonomous-refactor/package.json +1 -1
  13. package/packages/autonomous-runtime/package.json +1 -1
  14. package/packages/cache-strategy/package.json +1 -1
  15. package/packages/capability-registry/package.json +1 -1
  16. package/packages/checkpoint/package.json +1 -1
  17. package/packages/cli-plugin-sdk/package.json +1 -1
  18. package/packages/clipboard/package.json +1 -1
  19. package/packages/clipboard-plugin/package.json +1 -1
  20. package/packages/code-generation/package.json +1 -1
  21. package/packages/code-review/package.json +1 -1
  22. package/packages/codex-adapter/package.json +1 -1
  23. package/packages/codex-watcher/package.json +1 -1
  24. package/packages/config-capture/package.json +1 -1
  25. package/packages/connection-runtime/package.json +1 -1
  26. package/packages/context-compiler/package.json +1 -1
  27. package/packages/context-discovery/package.json +1 -1
  28. package/packages/context-manager/package.json +1 -1
  29. package/packages/contracts/dist/workspace-env-format.d.ts +63 -0
  30. package/packages/contracts/dist/workspace-env-format.js +133 -5
  31. package/packages/contracts/dist/workspace-env.js +114 -6
  32. package/packages/contracts/package.json +1 -1
  33. package/packages/contracts/src/workspace-env-format.ts +171 -5
  34. package/packages/contracts/src/workspace-env.ts +120 -4
  35. package/packages/contracts/test/workspace-env.test.ts +129 -0
  36. package/packages/cookie-sanitizer/package.json +1 -1
  37. package/packages/cost-optimizer/package.json +1 -1
  38. package/packages/dependency-analyzer/package.json +1 -1
  39. package/packages/django-plugin/package.json +1 -1
  40. package/packages/doc-generator/package.json +1 -1
  41. package/packages/durable-review/package.json +1 -1
  42. package/packages/e2e-tester/package.json +1 -1
  43. package/packages/evaluation-engine/package.json +1 -1
  44. package/packages/evaluation-runner/package.json +1 -1
  45. package/packages/event-bus/package.json +1 -1
  46. package/packages/event-sink/package.json +1 -1
  47. package/packages/event-store/package.json +1 -1
  48. package/packages/experience-replay/package.json +1 -1
  49. package/packages/feedback-collector/package.json +1 -1
  50. package/packages/file-copy-helper/package.json +1 -1
  51. package/packages/framework-detector/package.json +1 -1
  52. package/packages/framework-plugin-sdk/package.json +1 -1
  53. package/packages/frappe-plugin/package.json +1 -1
  54. package/packages/generic-web-plugin/package.json +1 -1
  55. package/packages/health-monitor/package.json +1 -1
  56. package/packages/intent-analyzer/package.json +1 -1
  57. package/packages/jev-judge/dist/continuation-controller.d.ts +47 -0
  58. package/packages/jev-judge/dist/continuation-controller.d.ts.map +1 -1
  59. package/packages/jev-judge/dist/continuation-controller.js +142 -16
  60. package/packages/jev-judge/dist/continuation-controller.js.map +1 -1
  61. package/packages/jev-judge/dist/index.d.ts +6 -0
  62. package/packages/jev-judge/dist/index.d.ts.map +1 -1
  63. package/packages/jev-judge/dist/index.js +6 -0
  64. package/packages/jev-judge/dist/index.js.map +1 -1
  65. package/packages/jev-judge/dist/validation-ledger.d.ts +129 -0
  66. package/packages/jev-judge/dist/validation-ledger.d.ts.map +1 -0
  67. package/packages/jev-judge/dist/validation-ledger.js +234 -0
  68. package/packages/jev-judge/dist/validation-ledger.js.map +1 -0
  69. package/packages/jev-judge/dist/validation-plan.d.ts +176 -0
  70. package/packages/jev-judge/dist/validation-plan.d.ts.map +1 -0
  71. package/packages/jev-judge/dist/validation-plan.js +293 -0
  72. package/packages/jev-judge/dist/validation-plan.js.map +1 -0
  73. package/packages/jev-judge/dist/validation-tie-breaker.d.ts +48 -0
  74. package/packages/jev-judge/dist/validation-tie-breaker.d.ts.map +1 -0
  75. package/packages/jev-judge/dist/validation-tie-breaker.js +83 -0
  76. package/packages/jev-judge/dist/validation-tie-breaker.js.map +1 -0
  77. package/packages/jev-judge/package.json +1 -1
  78. package/packages/jev-judge/src/continuation-controller.ts +179 -20
  79. package/packages/jev-judge/src/index.ts +41 -0
  80. package/packages/jev-judge/src/validation-ledger.ts +290 -0
  81. package/packages/jev-judge/src/validation-plan.ts +406 -0
  82. package/packages/jev-judge/src/validation-tie-breaker.ts +123 -0
  83. package/packages/jev-judge/tests/continuation-controller.test.ts +167 -0
  84. package/packages/jev-judge/tests/validation-ledger.test.ts +181 -0
  85. package/packages/jev-judge/tests/validation-plan.test.ts +170 -0
  86. package/packages/jev-judge/tests/validation-tie-breaker.test.ts +88 -0
  87. package/packages/knowledge-graph/package.json +1 -1
  88. package/packages/knowledge-retrieval/package.json +1 -1
  89. package/packages/laravel-plugin/package.json +1 -1
  90. package/packages/learning-engine/package.json +1 -1
  91. package/packages/logging/README.md +37 -0
  92. package/packages/logging/dist/buffer.d.ts +33 -0
  93. package/packages/logging/dist/buffer.d.ts.map +1 -0
  94. package/packages/logging/dist/buffer.js +60 -0
  95. package/packages/logging/dist/buffer.js.map +1 -0
  96. package/packages/logging/dist/config.d.ts +40 -0
  97. package/packages/logging/dist/config.d.ts.map +1 -0
  98. package/packages/logging/dist/config.js +114 -0
  99. package/packages/logging/dist/config.js.map +1 -0
  100. package/packages/logging/dist/index.d.ts +32 -0
  101. package/packages/logging/dist/index.d.ts.map +1 -0
  102. package/packages/logging/dist/index.js +31 -0
  103. package/packages/logging/dist/index.js.map +1 -0
  104. package/packages/logging/dist/redact.d.ts +43 -0
  105. package/packages/logging/dist/redact.d.ts.map +1 -0
  106. package/packages/logging/dist/redact.js +220 -0
  107. package/packages/logging/dist/redact.js.map +1 -0
  108. package/packages/logging/dist/sampler.d.ts +37 -0
  109. package/packages/logging/dist/sampler.d.ts.map +1 -0
  110. package/packages/logging/dist/sampler.js +78 -0
  111. package/packages/logging/dist/sampler.js.map +1 -0
  112. package/packages/logging/dist/shipper.d.ts +82 -0
  113. package/packages/logging/dist/shipper.d.ts.map +1 -0
  114. package/packages/logging/dist/shipper.js +238 -0
  115. package/packages/logging/dist/shipper.js.map +1 -0
  116. package/packages/logging/dist/singleton.d.ts +38 -0
  117. package/packages/logging/dist/singleton.d.ts.map +1 -0
  118. package/packages/logging/dist/singleton.js +95 -0
  119. package/packages/logging/dist/singleton.js.map +1 -0
  120. package/packages/logging/dist/spool.d.ts +39 -0
  121. package/packages/logging/dist/spool.d.ts.map +1 -0
  122. package/packages/logging/dist/spool.js +110 -0
  123. package/packages/logging/dist/spool.js.map +1 -0
  124. package/packages/logging/dist/transports/r2.d.ts +31 -0
  125. package/packages/logging/dist/transports/r2.d.ts.map +1 -0
  126. package/packages/logging/dist/transports/r2.js +60 -0
  127. package/packages/logging/dist/transports/r2.js.map +1 -0
  128. package/packages/logging/dist/transports/webhook.d.ts +22 -0
  129. package/packages/logging/dist/transports/webhook.d.ts.map +1 -0
  130. package/packages/logging/dist/transports/webhook.js +37 -0
  131. package/packages/logging/dist/transports/webhook.js.map +1 -0
  132. package/packages/logging/dist/types.d.ts +73 -0
  133. package/packages/logging/dist/types.d.ts.map +1 -0
  134. package/packages/logging/dist/types.js +19 -0
  135. package/packages/logging/dist/types.js.map +1 -0
  136. package/packages/logging/package.json +37 -0
  137. package/packages/logging/src/buffer.ts +77 -0
  138. package/packages/logging/src/config.ts +163 -0
  139. package/packages/logging/src/index.ts +87 -0
  140. package/packages/logging/src/redact.ts +271 -0
  141. package/packages/logging/src/sampler.ts +97 -0
  142. package/packages/logging/src/shipper.ts +281 -0
  143. package/packages/logging/src/singleton.ts +142 -0
  144. package/packages/logging/src/spool.ts +139 -0
  145. package/packages/logging/src/transports/r2.ts +86 -0
  146. package/packages/logging/src/transports/webhook.ts +54 -0
  147. package/packages/logging/src/types.ts +85 -0
  148. package/packages/logging/test/buffer.test.ts +44 -0
  149. package/packages/logging/test/config.test.ts +97 -0
  150. package/packages/logging/test/integration.test.ts +135 -0
  151. package/packages/logging/test/redact.test.ts +109 -0
  152. package/packages/logging/test/sampler.test.ts +57 -0
  153. package/packages/logging/test/shipper.test.ts +152 -0
  154. package/packages/logging/test/spool.test.ts +93 -0
  155. package/packages/mcp-adapter/package.json +1 -1
  156. package/packages/memory-engine/package.json +1 -1
  157. package/packages/milestone-manager/package.json +1 -1
  158. package/packages/model-registry/package.json +1 -1
  159. package/packages/nextjs-plugin/package.json +1 -1
  160. package/packages/notification/package.json +1 -1
  161. package/packages/observability/package.json +1 -1
  162. package/packages/okf-indexer/package.json +1 -1
  163. package/packages/paperclip-integration/package.json +1 -1
  164. package/packages/performance-optimizer/package.json +1 -1
  165. package/packages/permission-mode/package.json +1 -1
  166. package/packages/privilege-broker/package.json +1 -1
  167. package/packages/project-analyzer/package.json +1 -1
  168. package/packages/project-bootstrap/package.json +1 -1
  169. package/packages/projection-engine/package.json +1 -1
  170. package/packages/prompt-compiler/package.json +1 -1
  171. package/packages/prompt-versioning/package.json +1 -1
  172. package/packages/provider-adapter-sdk/package.json +1 -1
  173. package/packages/provider-router/package.json +1 -1
  174. package/packages/provider-selector/package.json +1 -1
  175. package/packages/providers/package.json +1 -1
  176. package/packages/qdrant-skills/package.json +1 -1
  177. package/packages/quota-manager/package.json +1 -1
  178. package/packages/rate-limiter/package.json +1 -1
  179. package/packages/react-vite-plugin/package.json +1 -1
  180. package/packages/relay/dist/client.d.ts +25 -1
  181. package/packages/relay/dist/client.js +71 -23
  182. package/packages/relay/dist/index.d.ts +1 -1
  183. package/packages/relay/package.json +1 -1
  184. package/packages/relay/src/client.ts +87 -23
  185. package/packages/relay/src/index.ts +1 -1
  186. package/packages/relay/test/client.test.ts +134 -0
  187. package/packages/release-manager/package.json +1 -1
  188. package/packages/requirement-compiler/package.json +1 -1
  189. package/packages/runtime/package.json +1 -1
  190. package/packages/scheduler/package.json +1 -1
  191. package/packages/scheduler-adapter/package.json +1 -1
  192. package/packages/session/package.json +1 -1
  193. package/packages/session-api/package.json +1 -1
  194. package/packages/session-export/package.json +1 -1
  195. package/packages/shared-context/package.json +1 -1
  196. package/packages/skill-mcp-client/package.json +1 -1
  197. package/packages/skill-registry/package.json +1 -1
  198. package/packages/skills/package.json +1 -1
  199. package/packages/sprint-planner/package.json +1 -1
  200. package/packages/subscription-engine/package.json +1 -1
  201. package/packages/task-compiler/package.json +1 -1
  202. package/packages/telegram/package.json +1 -1
  203. package/packages/tencentdb-memory/package.json +1 -1
  204. package/packages/tencentdb-sync/package.json +1 -1
  205. package/packages/test-data-generator/package.json +1 -1
  206. package/packages/test-generator/package.json +1 -1
  207. package/packages/todo-bd-sync/package.json +1 -1
  208. package/packages/token-estimation/package.json +1 -1
  209. package/packages/token-optimizer/package.json +1 -1
  210. package/packages/trajectory/package.json +1 -1
  211. package/packages/tui/package.json +1 -1
  212. package/packages/types/package.json +1 -1
  213. package/packages/voice-api/package.json +1 -1
  214. package/packages/webmcp-tester/package.json +1 -1
  215. package/packages/wiki/package.json +1 -1
  216. package/packages/workflow-events/package.json +1 -1
  217. package/packages/workspace-scanner/package.json +1 -1
  218. package/packages/worktree/package.json +1 -1
  219. package/packages/write-review/package.json +1 -1
  220. package/scripts/unified-webhook-server.ts +102 -0
  221. package/scripts/validation-run.ts +191 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-harness-runtime",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "[BETA] Codex-style /usage status + autonomous coding harness for pi. Not production ready — expect breaking changes.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/a2a-adapter",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "Google Agent-to-Agent (A2A) protocol adapter for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/agent-policy",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Agent policy runtime for pi-harness - enforces mandatory rule delivery and mutation gating",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/architecture-generator",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Generate ADR and Mermaid diagrams from project analysis",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/auth",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "pi-harness-runtime — auth",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/autonomous-refactor",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "Autonomous refactoring engine",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/autonomous-runtime",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Autonomous Operations Runtime — task inbox, lease management, and worker lifecycle (RFC-0101)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/cache-strategy",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "Smart caching with TTL and invalidation strategies (RFC-0022)",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/capability-registry",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Capability Registry for pi-harness-runtime — tracks model capabilities and enables capability-based routing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/checkpoint",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Enhanced checkpoint system with incremental snapshots, diff-based storage, and recovery strategies",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/cli-plugin-sdk",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "CLI Plugin SDK for pi-harness — install, list, remove, update, invoke plugins",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/clipboard",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/clipboard-plugin",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/code-generation",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Code generation pipeline for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/code-review",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Automated code review engine for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/codex-adapter",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "OpenAI Codex provider adapter for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/codex-watcher",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Watches Codex CLI sessions and emits visible messages for PING-PONG monitoring",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/config-capture",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Auto-capture API-fetched configuration values to AGENTS.md",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/connection-runtime",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "Connection management with jittered exponential backoff for pi-harness",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/context-compiler",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Bounded context selection engine for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/context-discovery",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/context-manager",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "description": "Context Window Manager — prevents model context overflow via compaction and resume prompts (RFC-0010)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -135,6 +135,14 @@ export declare const MASKED_MODE_VALUE = "<set>";
135
135
  * the name of an environment before its value may decide the mode.
136
136
  */
137
137
  export declare function isEnvShapedKey(key: string): boolean;
138
+ /**
139
+ * True for a key that belongs to the desktop session, not to the workspace.
140
+ *
141
+ * The rule is a namespace list, not a value check: the value of a session
142
+ * variable ("ubuntu", "wayland", "user") says nothing about the environment
143
+ * of a workspace, so such a variable must never name the mode.
144
+ */
145
+ export declare function isSessionVariableKey(key: string): boolean;
138
146
  /**
139
147
  * True for a key that may hold a secret.
140
148
  */
@@ -202,6 +210,61 @@ export interface ModeResolution {
202
210
  hint: DotenvVar | null;
203
211
  source: "process.env" | ".env file" | null;
204
212
  }
213
+ /**
214
+ * The deployment configuration of a repository.
215
+ *
216
+ * A repository that carries one of these files is deployed somewhere: it
217
+ * has a runtime environment of its own, so the shape of the repository
218
+ * cannot prove that the workspace is a development checkout.
219
+ */
220
+ export declare const DEPLOYMENT_MARKER_FILES: readonly ["Dockerfile", "Containerfile", "docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml", "Procfile", "fly.toml", "render.yaml", "railway.json", "vercel.json", "netlify.toml", "serverless.yml", "serverless.yaml", "app.yaml", "ecosystem.config.cjs", "ecosystem.config.js"];
221
+ /**
222
+ * The facts about the repository the workspace belongs to.
223
+ *
224
+ * They are read from the file system (and from git), never from a variable:
225
+ * a published package that is deployed nowhere is source code that the
226
+ * user runs on their own machine.
227
+ */
228
+ export interface RepositoryFacts {
229
+ /** The workspace is a git checkout. */
230
+ isGitRepository: boolean;
231
+ /** The current branch ("" when it cannot be read). */
232
+ branch: string;
233
+ /** The package.json describes a package that is published to a registry. */
234
+ isPublishedPackage: boolean;
235
+ /** The repository carries a deployment configuration. */
236
+ hasDeploymentMarker: boolean;
237
+ /** A dev or build server runs in this workspace. */
238
+ hasRunningServer: boolean;
239
+ }
240
+ /**
241
+ * A mode that the facts about the repository named.
242
+ */
243
+ export interface RepositoryModeDetection {
244
+ mode: WorkspaceMode;
245
+ /**
246
+ * The phrase that names the detection.
247
+ *
248
+ * It is used as the key of the evidence ("source-only repository",
249
+ * "git branch develop"), so the label can name it the same way it
250
+ * names a variable.
251
+ */
252
+ key: string;
253
+ }
254
+ /**
255
+ * Name the mode of a workspace from the facts about its repository.
256
+ *
257
+ * This is the LAST tier of the mode detection: it runs only when no
258
+ * variable names the environment at all. It never claims production - a
259
+ * checkout of a production branch is not a production environment, and the
260
+ * deployment marker of the repository keeps the answer honest.
261
+ *
262
+ * Two sets of facts name a development workspace:
263
+ * 1. a published package without a deployment configuration and without a
264
+ * running server: source code that the user runs on their own machine
265
+ * 2. a branch that names a development line of work ("develop")
266
+ */
267
+ export declare function modeFromRepositoryFacts(facts: RepositoryFacts): RepositoryModeDetection | null;
205
268
  /**
206
269
  * Resolve the workspace mode from the variables that were found.
207
270
  *
@@ -167,6 +167,49 @@ export const MASKED_MODE_VALUE = "<set>";
167
167
  export function isEnvShapedKey(key) {
168
168
  return /(^|_)(ENV|ENVIRONMENT|MODE|STAGE)$/i.test(key);
169
169
  }
170
+ /**
171
+ * The namespaces of the desktop session variables.
172
+ *
173
+ * A session variable names the desktop session ("GNOME_SHELL_SESSION_MODE",
174
+ * "XDG_SESSION_TYPE"), never the environment of a workspace. Some of them
175
+ * end in "_MODE" or "_ENV", so without this list they look like an
176
+ * environment selector and decide the mode of a workspace that has no
177
+ * environment at all.
178
+ */
179
+ const SESSION_VARIABLE_PREFIXES = [
180
+ "GNOME_",
181
+ "XDG_",
182
+ "DESKTOP_",
183
+ "DBUS_",
184
+ "GDM_",
185
+ "SDDM_",
186
+ "LIGHTDM_",
187
+ "GTK_",
188
+ "QT_",
189
+ "MATE_",
190
+ "KDE_",
191
+ "PLASMA_",
192
+ "UNITY_",
193
+ "XFCE_",
194
+ "CINNAMON_",
195
+ "WAYLAND_",
196
+ "MIR_",
197
+ "I3_",
198
+ "TERM_",
199
+ "SSH_",
200
+ "TMUX_",
201
+ ];
202
+ /**
203
+ * True for a key that belongs to the desktop session, not to the workspace.
204
+ *
205
+ * The rule is a namespace list, not a value check: the value of a session
206
+ * variable ("ubuntu", "wayland", "user") says nothing about the environment
207
+ * of a workspace, so such a variable must never name the mode.
208
+ */
209
+ export function isSessionVariableKey(key) {
210
+ const upper = key.toUpperCase();
211
+ return SESSION_VARIABLE_PREFIXES.some((prefix) => upper.startsWith(prefix));
212
+ }
170
213
  /**
171
214
  * True for a key that may hold a secret.
172
215
  */
@@ -212,6 +255,9 @@ export function detectModeHint(vars, options = {}) {
212
255
  const nodeEnv = vars.find((v) => v.key === "NODE_ENV");
213
256
  if (nodeEnv)
214
257
  return nodeEnv;
258
+ // A session variable is not an environment selector: it must never
259
+ // become the hint that names the mode.
260
+ const usableVars = vars.filter((v) => !isSessionVariableKey(v.key));
215
261
  // The value match is opt-in: a caller that passes the process env
216
262
  // variables must not let an unrelated variable that happens to hold the
217
263
  // word "development" decide the mode.
@@ -219,7 +265,7 @@ export function detectModeHint(vars, options = {}) {
219
265
  // An environment shaped key whose value names a known mode wins: an
220
266
  // earlier "APP_MODE=staging" or an empty "APP_ENV=" of the same file
221
267
  // must not shadow the "AMOS_ENV=development" of the same file.
222
- const usable = vars.find((v) => v.key !== "NODE_ENV" &&
268
+ const usable = usableVars.find((v) => v.key !== "NODE_ENV" &&
223
269
  isEnvShapedKey(v.key) &&
224
270
  modeFromValue(v.value) !== null);
225
271
  if (usable)
@@ -227,7 +273,7 @@ export function detectModeHint(vars, options = {}) {
227
273
  }
228
274
  // No usable key: fall back to the first environment shaped key so that
229
275
  // the label can name it (it names no known mode).
230
- return vars.find((v) => v.key !== "NODE_ENV" && isEnvShapedKey(v.key)) ?? null;
276
+ return (usableVars.find((v) => v.key !== "NODE_ENV" && isEnvShapedKey(v.key)) ?? null);
231
277
  }
232
278
  /**
233
279
  * The dotenv files of a workspace, in the order they are read.
@@ -289,12 +335,71 @@ export function collectProcessEnvVars(env = process.env) {
289
335
  const value = env[key];
290
336
  if (value === undefined)
291
337
  continue;
338
+ // A session variable of the desktop is never an environment
339
+ // selector, so it is not even collected as a candidate.
340
+ if (isSessionVariableKey(key))
341
+ continue;
292
342
  if (key === "NODE_ENV" || isEnvShapedKey(key)) {
293
343
  vars.push({ key, value });
294
344
  }
295
345
  }
296
346
  return vars;
297
347
  }
348
+ /**
349
+ * The deployment configuration of a repository.
350
+ *
351
+ * A repository that carries one of these files is deployed somewhere: it
352
+ * has a runtime environment of its own, so the shape of the repository
353
+ * cannot prove that the workspace is a development checkout.
354
+ */
355
+ export const DEPLOYMENT_MARKER_FILES = [
356
+ "Dockerfile",
357
+ "Containerfile",
358
+ "docker-compose.yml",
359
+ "docker-compose.yaml",
360
+ "compose.yml",
361
+ "compose.yaml",
362
+ "Procfile",
363
+ "fly.toml",
364
+ "render.yaml",
365
+ "railway.json",
366
+ "vercel.json",
367
+ "netlify.toml",
368
+ "serverless.yml",
369
+ "serverless.yaml",
370
+ "app.yaml",
371
+ "ecosystem.config.cjs",
372
+ "ecosystem.config.js",
373
+ ];
374
+ /**
375
+ * The branches that name a development line of work.
376
+ */
377
+ const DEVELOPMENT_BRANCHES = new Set(["develop", "development"]);
378
+ /**
379
+ * Name the mode of a workspace from the facts about its repository.
380
+ *
381
+ * This is the LAST tier of the mode detection: it runs only when no
382
+ * variable names the environment at all. It never claims production - a
383
+ * checkout of a production branch is not a production environment, and the
384
+ * deployment marker of the repository keeps the answer honest.
385
+ *
386
+ * Two sets of facts name a development workspace:
387
+ * 1. a published package without a deployment configuration and without a
388
+ * running server: source code that the user runs on their own machine
389
+ * 2. a branch that names a development line of work ("develop")
390
+ */
391
+ export function modeFromRepositoryFacts(facts) {
392
+ if (!facts.isGitRepository)
393
+ return null;
394
+ if (facts.isPublishedPackage && !facts.hasDeploymentMarker && !facts.hasRunningServer) {
395
+ return { mode: "development", key: "source-only repository" };
396
+ }
397
+ const branch = facts.branch.trim().toLowerCase();
398
+ if (DEVELOPMENT_BRANCHES.has(branch)) {
399
+ return { mode: "development", key: `git branch ${branch}` };
400
+ }
401
+ return null;
402
+ }
298
403
  /**
299
404
  * Resolve the workspace mode from the variables that were found.
300
405
  *
@@ -346,6 +451,16 @@ function findModeEvidence(env) {
346
451
  // A value that names a known mode wins over a value that cannot be read.
347
452
  return candidates.find((e) => modeFromValue(e.value) !== null) ?? candidates[0];
348
453
  }
454
+ /**
455
+ * The evidence entry that names the mode through the repository (if any).
456
+ *
457
+ * A detection is weaker than a variable: it is the shape of the checkout,
458
+ * not a declaration of the environment. It is therefore only used when no
459
+ * variable names the environment at all.
460
+ */
461
+ function findDetectionEvidence(env) {
462
+ return env.evidence.find((e) => e.source === "detection");
463
+ }
349
464
  /**
350
465
  * Human readable mode label.
351
466
  *
@@ -361,9 +476,14 @@ export function describeWorkspaceMode(env) {
361
476
  const hint = findModeEvidence(env);
362
477
  const where = hint?.file ? ` in ${hint.file}` : "";
363
478
  if (env.mode !== "unknown") {
364
- return hint
365
- ? `${env.mode.toUpperCase()} (from ${hint.key}${where})`
366
- : env.mode.toUpperCase();
479
+ if (hint)
480
+ return `${env.mode.toUpperCase()} (from ${hint.key}${where})`;
481
+ // The mode came from the repository, not from a variable: the label
482
+ // names the reason ("DEVELOPMENT (source-only repository)").
483
+ const detection = findDetectionEvidence(env);
484
+ if (detection)
485
+ return `${env.mode.toUpperCase()} (${detection.key})`;
486
+ return env.mode.toUpperCase();
367
487
  }
368
488
  if (hint) {
369
489
  // The stored mode and the evidence can disagree (a stale cache row):
@@ -382,6 +502,14 @@ export function describeWorkspaceMode(env) {
382
502
  }
383
503
  return `UNKNOWN (${hint.key}=${hint.value} is not a known mode${where})`;
384
504
  }
505
+ // A stale cache row can carry a detection next to an unknown mode: the
506
+ // detection names a mode, so the mode is shown.
507
+ const detection = findDetectionEvidence(env);
508
+ if (detection) {
509
+ const named = modeFromValue(detection.value);
510
+ if (named)
511
+ return `${named.toUpperCase()} (${detection.key})`;
512
+ }
385
513
  return "UNKNOWN (no NODE_ENV or environment variable found)";
386
514
  }
387
515
  export function describeServerState(stateOrRunning, hasScript) {
@@ -13,10 +13,10 @@
13
13
  */
14
14
  import { DatabaseSync } from "node:sqlite";
15
15
  import { execSync } from "child_process";
16
- import { existsSync, readFileSync, mkdirSync, readlinkSync, realpathSync } from "fs";
16
+ import { existsSync, readFileSync, readdirSync, mkdirSync, readlinkSync, realpathSync } from "fs";
17
17
  import { join } from "path";
18
18
  import { platform, totalmem, freemem } from "os";
19
- import { canShowModeValue, collectDotenvVars, collectProcessEnvVars, describeCwdCommand, describeOs, describeServerState, describeWorkspaceMode, detectModeHint, filterOwnPids, formatEnvForAgentResult, formatEnvStatusLine as formatStatusLine, formatWorkspacePath, isLauncherCommand, MASKED_MODE_VALUE, modeFromValue, orderDotenvFileNames, parseDotenvVars, resolveWorkspaceMode, } from "./workspace-env-format.js";
19
+ import { canShowModeValue, collectDotenvVars, collectProcessEnvVars, describeCwdCommand, describeOs, describeServerState, describeWorkspaceMode, detectModeHint, DEPLOYMENT_MARKER_FILES, filterOwnPids, formatEnvForAgentResult, formatEnvStatusLine as formatStatusLine, formatWorkspacePath, isLauncherCommand, MASKED_MODE_VALUE, modeFromRepositoryFacts, modeFromValue, orderDotenvFileNames, parseDotenvVars, resolveWorkspaceMode, } from "./workspace-env-format.js";
20
20
  import { readDotenvFiles } from "./workspace-dotenv.js";
21
21
  const STORE_DIR = join(process.env.HOME || "/home/frappe", ".pi-harness-runtime");
22
22
  /**
@@ -520,6 +520,97 @@ function detectPackageManager(workspacePath) {
520
520
  return "npm";
521
521
  return null;
522
522
  }
523
+ /**
524
+ * The git repository of a workspace.
525
+ *
526
+ * The root of the repository is the unit of the detection: a package
527
+ * published from a monorepo is described by the root, not by the folder
528
+ * the harness happens to run in.
529
+ */
530
+ function detectGitFacts(workspacePath) {
531
+ try {
532
+ const out = execSync("git rev-parse --show-toplevel 2>/dev/null; git rev-parse --abbrev-ref HEAD 2>/dev/null", { cwd: workspacePath, encoding: "utf8", timeout: 1500 });
533
+ const lines = out.split("\n").map((line) => line.trim());
534
+ const root = lines[0] ?? "";
535
+ const branch = lines[1] ?? "";
536
+ // A detached HEAD reports "HEAD" - that is not a branch name.
537
+ return { root, branch: branch === "HEAD" ? "" : branch };
538
+ }
539
+ catch {
540
+ // No git, no repository, or a timeout: nothing is known.
541
+ return { root: "", branch: "" };
542
+ }
543
+ }
544
+ /**
545
+ * True when the package is published to a registry.
546
+ *
547
+ * A package that is published names a version and declares what it ships
548
+ * ("files", "bin" or a "publishConfig"). A repository that only holds an
549
+ * application usually declares none of them - so this stays a careful
550
+ * signal instead of "every package.json is published".
551
+ */
552
+ function isPublishedPackage(packageDir) {
553
+ const pkgJsonPath = join(packageDir, "package.json");
554
+ if (!existsSync(pkgJsonPath))
555
+ return false;
556
+ try {
557
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
558
+ if (!pkg || typeof pkg !== "object")
559
+ return false;
560
+ if (pkg.private === true)
561
+ return false;
562
+ if (typeof pkg.name !== "string" || !pkg.name)
563
+ return false;
564
+ if (typeof pkg.version !== "string" || !pkg.version)
565
+ return false;
566
+ return (Array.isArray(pkg.files) ||
567
+ (typeof pkg.bin === "object" && pkg.bin !== null) ||
568
+ (typeof pkg.publishConfig === "object" && pkg.publishConfig !== null));
569
+ }
570
+ catch {
571
+ return false;
572
+ }
573
+ }
574
+ /**
575
+ * True when the repository carries a deployment configuration.
576
+ *
577
+ * A deployment file or a deploy workflow means the repository has a
578
+ * runtime environment of its own - the shape of the repository can then no
579
+ * longer say that the workspace is a development checkout.
580
+ */
581
+ function hasDeploymentConfiguration(repoRoot) {
582
+ for (const marker of DEPLOYMENT_MARKER_FILES) {
583
+ if (existsSync(join(repoRoot, marker)))
584
+ return true;
585
+ }
586
+ const workflowDir = join(repoRoot, ".github", "workflows");
587
+ if (existsSync(workflowDir)) {
588
+ try {
589
+ for (const name of readdirSync(workflowDir)) {
590
+ if (/deploy/i.test(name))
591
+ return true;
592
+ }
593
+ }
594
+ catch {
595
+ // An unreadable directory proves nothing.
596
+ }
597
+ }
598
+ return false;
599
+ }
600
+ /**
601
+ * Read the facts about the repository the workspace belongs to.
602
+ */
603
+ function detectRepositoryFacts(workspacePath, hasRunningServer) {
604
+ const { root, branch } = detectGitFacts(workspacePath);
605
+ const repoRoot = root || workspacePath;
606
+ return {
607
+ isGitRepository: root !== "",
608
+ branch,
609
+ isPublishedPackage: isPublishedPackage(repoRoot),
610
+ hasDeploymentMarker: hasDeploymentConfiguration(repoRoot),
611
+ hasRunningServer,
612
+ };
613
+ }
523
614
  /**
524
615
  * Determine workspace mode with proper precedence
525
616
  * Never assume production - unknown requires investigation
@@ -531,8 +622,11 @@ function detectPackageManager(workspacePath) {
531
622
  * key whose value names a known mode, then any other environment shaped
532
623
  * key
533
624
  * 3. an environment shaped key of the process env (the files are silent)
625
+ * 4. the facts about the repository (only when NO variable names the
626
+ * environment at all) - a published source repository without a runtime
627
+ * of its own is a development checkout
534
628
  */
535
- function determineMode(processVars, dotenvVars, evidence) {
629
+ function determineMode(processVars, dotenvVars, evidence, repositoryFacts) {
536
630
  const resolved = resolveWorkspaceMode(processVars, dotenvVars);
537
631
  if (resolved.hint && resolved.source) {
538
632
  // The value is only kept when it is provably not a secret: the
@@ -546,8 +640,21 @@ function determineMode(processVars, dotenvVars, evidence) {
546
640
  timestamp: Date.now(),
547
641
  file: resolved.hint.file,
548
642
  });
549
- }
550
- return resolved.mode;
643
+ // An explicit variable decides - even when it names no known mode
644
+ // ("staging"). The mode stays unknown and the label names the value.
645
+ return resolved.mode;
646
+ }
647
+ // No variable names the environment: the repository may still name it.
648
+ const detection = modeFromRepositoryFacts(repositoryFacts);
649
+ if (!detection)
650
+ return resolved.mode;
651
+ evidence.push({
652
+ source: "detection",
653
+ key: detection.key,
654
+ value: detection.mode,
655
+ timestamp: Date.now(),
656
+ });
657
+ return detection.mode;
551
658
  }
552
659
  /**
553
660
  * Detect and store workspace environment
@@ -594,7 +701,8 @@ export function detectWorkspaceEnv(workspacePath) {
594
701
  // workspace (see detectServerStates).
595
702
  const { dev: devServerState, build: buildServerState } = detectServerStates(workspacePath);
596
703
  // Determine mode - NEVER assume production
597
- const mode = determineMode(processVars, dotenvVars, evidence);
704
+ const repositoryFacts = detectRepositoryFacts(workspacePath, devServerState === "running" || buildServerState === "running");
705
+ const mode = determineMode(processVars, dotenvVars, evidence, repositoryFacts);
598
706
  // Determine build permission
599
707
  let buildAllowed = false;
600
708
  if (mode === "production") {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/contracts",
3
- "version": "1.1.393",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "RPC schema definitions for pi-harness runtime",
6
6
  "main": "./dist/index.js",