@gajae-code/coding-agent 0.12.0 → 0.12.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.
- package/CHANGELOG.md +82 -1
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/config/settings-schema.d.ts +54 -0
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +16 -2
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +18 -0
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
- package/dist/types/modes/acp/acp-agent.d.ts +2 -19
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +38 -2
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
- package/dist/types/session/session-manager.d.ts +23 -6
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +36 -7
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +6 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +7 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/output-meta.d.ts +23 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +0 -6
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +10 -8
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +285 -0
- package/src/async/job-manager.ts +78 -20
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/commands/team.ts +19 -1
- package/src/config/file-lock.ts +11 -2
- package/src/config/settings-schema.ts +51 -0
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
- package/src/defaults/gjc/skills/team/SKILL.md +2 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +18 -1
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/ralplan-runtime.ts +411 -17
- package/src/gjc-runtime/team-launch.ts +8 -0
- package/src/gjc-runtime/team-runtime.ts +140 -32
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-runtime.ts +401 -611
- package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
- package/src/gjc-runtime/workflow-manifest.ts +4 -68
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/acp-agent.ts +298 -186
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +68 -30
- package/src/modes/components/session-observer-overlay.ts +305 -86
- package/src/modes/components/settings-selector.ts +56 -5
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-status-header.ts +3 -14
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/extension-ui-controller.ts +7 -0
- package/src/modes/controllers/selector-controller.ts +45 -0
- package/src/modes/runtime-init.ts +3 -0
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -4
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +6 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/index.ts +661 -126
- package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/reconciliation-store.ts +95 -16
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session.ts +88 -9
- package/src/session/agent-session.ts +822 -172
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +44 -6
- package/src/session/internal/managed-session-storage.ts +48 -6
- package/src/session/session-manager.ts +1614 -435
- package/src/session/session-storage.ts +9 -1
- package/src/session/streaming-output.ts +238 -68
- package/src/setup/credential-import.ts +75 -13
- package/src/skill-state/active-state.ts +32 -0
- package/src/skill-state/workflow-hud.ts +17 -0
- package/src/skill-state/workflow-mutation-guard.ts +496 -2
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +23 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +23 -7
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +86 -55
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/output-meta.ts +108 -14
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +18 -0
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -34,6 +34,7 @@ gjc ultragoal create-goals --brief "<brief>"
|
|
|
34
34
|
gjc ultragoal create-goals --brief-file <path>
|
|
35
35
|
gjc ultragoal complete-goals
|
|
36
36
|
gjc ultragoal complete-goals --retry-failed
|
|
37
|
+
gjc ultragoal quality-gate validate --quality-gate-json <quality-gate-json-or-path> [--goal-id <id>] [--json]
|
|
37
38
|
gjc ultragoal checkpoint --goal-id <id> --status complete --evidence "<evidence>" --quality-gate-json <quality-gate-json-or-path>
|
|
38
39
|
gjc ultragoal checkpoint --goal-id <id> --status failed --evidence "<blocker/evidence>"
|
|
39
40
|
gjc ultragoal record-review-blockers --goal-id <id> --title "Resolve final review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>"
|
|
@@ -193,7 +194,7 @@ When delegating:
|
|
|
193
194
|
- Give each `executor` bounded targets and explicit acceptance criteria, and keep checkpoint/goal-state ownership in the leader.
|
|
194
195
|
- Parallelize only across genuinely different sub-domains/modules/systems; sequence anything with a real dependency or shared-surface overlap.
|
|
195
196
|
- Work within a single domain/subsystem stays with the leader as direct edits — do not split one cohesive change across subagents, and do not over-delegate trivial work.
|
|
196
|
-
- After integrating delegated slices, run `architect` / `critic` review lanes
|
|
197
|
+
- After integrating delegated slices, you MAY run `architect` / `critic` review lanes for early signal, but treat them as **advisory**: the canonical review is the boundary cohort gate below, and a slice-level lane never substitutes for it or its verdict. Skip slice review entirely when the boundary cohort will cover the same change set shortly. Worker agents never mutate `.gjc/_session-{sessionid}/ultragoal` or call goal tools.
|
|
197
198
|
|
|
198
199
|
When delegating with native subagents, an await timeout only limits the leader's wait. It is not subagent failure evidence and must not be used as a cancellation reason; inspect or continue independent work, and cancel only when the subagent has actually failed, gone off-track, or become unrecoverably wrong.
|
|
199
200
|
|
|
@@ -223,21 +224,19 @@ Before workers start, each per-slice coordination contract MUST name the target
|
|
|
223
224
|
|
|
224
225
|
For failed, timed-out, or contract-violating slices, record durable ledger evidence; preserve successful terminal slices only when safe; and reassign, retry, or collapse the invalid work to serial execution under an updated contract. Completion after parallel work still requires terminal worker evidence, leader integration, targeted verification, and the existing cleaner + architect + executor QA/red-team gate before checkpoint complete.
|
|
225
226
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
Sequential execution remains the default. Ultragoal may use runtime-backed pipelined scheduling only when `goals.json` metadata proves original-plan independence and disjoint target files/surfaces for the prior and next goals. This is a leader-owned Ultragoal runtime contract, not hidden Team scheduling and not a substitute for the native executor parallelism contract above.
|
|
227
|
+
Team remains explicit and separate: Team is not auto-launched, not a hidden pipeline scheduler, and never owns Ultragoal goals, checkpoints, or ledger state.
|
|
229
228
|
|
|
230
|
-
|
|
229
|
+
## Boundary verification (aggregate default)
|
|
231
230
|
|
|
232
|
-
|
|
231
|
+
Heavyweight review runs **once per boundary**, not once per story. In aggregate mode the whole required-goal set is one implicit boundary by default: every checkpoint before the run's final required goal may present the lightweight `deferredToBatch` gate, and only the final goal carries the full strict gate. Nothing needs to be declared to get this — it is the default.
|
|
233
232
|
|
|
234
|
-
|
|
233
|
+
Per-subgoal verification is **agent-chosen**. The runtime does not dictate which lanes an intermediate story runs; it enforces only that the declaration matches the evidence. List the lanes you actually ran in `deferredToBatch.ranLanes` (`targetedVerification`, and optionally `aiSlopCleaner` / `iteration`). A declared lane with no evidence is rejected, submitted evidence for an undeclared lane is rejected, and `ranLanes` can never claim `architectReview` or `executorQa` — a deferred gate structurally cannot carry review evidence, so those lanes always belong to the boundary. `targetedVerification` remains mandatory; the ai-slop-cleaner pass and a full verification rerun are boundary duties, not per-subgoal duties.
|
|
235
234
|
|
|
236
|
-
|
|
235
|
+
Deferring never manufactures approvals: a `deferredToBatch` gate still cannot contain `architectReview`, `executorQa`, or `validationBatchClose`, and the boundary gate keeps every existing fail-closed rule.
|
|
237
236
|
|
|
238
|
-
|
|
237
|
+
### Validation batches (explicit phase/module boundaries)
|
|
239
238
|
|
|
240
|
-
|
|
239
|
+
When one ledger is large enough that a single end-of-run boundary is too coarse, use an explicit validation batch to subdivide it into phase/module boundaries, each with its own final member. Validation batches are **aggregate-only**, **explicit-only**, and **fail-closed**. They are created only through `--validation-batch-json`; there is no inference from brief prose, no per-story batching, and no other batching input path.
|
|
241
240
|
|
|
242
241
|
Create a batch explicitly:
|
|
243
242
|
|
|
@@ -245,14 +244,14 @@ Create a batch explicitly:
|
|
|
245
244
|
gjc ultragoal create-goals --brief-file <path> --validation-batch-json '[{"schemaVersion":1,"batchId":"VB001","memberIds":["G001","G002","G003"],"finalGoalId":"G003"}]'
|
|
246
245
|
```
|
|
247
246
|
|
|
248
|
-
Checkpoint contract summary — the full contract lives in the `
|
|
247
|
+
Checkpoint contract summary — the full contract lives in the `validation-batch-contracts` fragment (`skill-fragments/ultragoal/validation-batch-contracts.md`); load it before checkpointing any batch member:
|
|
249
248
|
|
|
250
|
-
- **Non-final members** checkpoint `complete` with a single top-level `deferredToBatch` quality gate (kind `validation-batch-deferred`) proving targeted verification,
|
|
249
|
+
- **Non-final members** checkpoint `complete` with a single top-level `deferredToBatch` quality gate (kind `validation-batch-deferred`) proving targeted verification, a declaration-matched lane set, and a cumulative-since-base change set — never `architectReview`, `executorQa`, or `validationBatchClose`; deferring never manufactures fake review approvals.
|
|
251
250
|
- **The final member** (`finalGoalId`) checkpoints `complete` with the normal full strict gate PLUS a top-level `validationBatchClose` proof covering all members; out-of-order close is rejected, close state is append-only proof on the final member only, and batch invalidation is fail-closed.
|
|
252
251
|
|
|
253
252
|
### Intra-goal validation-lane parallelism
|
|
254
253
|
|
|
255
|
-
|
|
254
|
+
Cohort lanes are parallel by construction: the boundary gate freezes one `sourceHash` first, so `cleaner`, `architect`, and `qa` can run concurrently against the identical immutable snapshot and then join. Fall back to **sequential** lanes only when code is still changing (nothing can be frozen yet), when the red-team lane depends on architect fixes, or when architect findings gate the QA scope. Either way the lanes must **join before checkpoint** — no lane checkpoints independently, and repair work starts only after the join.
|
|
256
255
|
|
|
257
256
|
## Use Ultragoal and Team together
|
|
258
257
|
|
|
@@ -276,13 +275,15 @@ The completion-gate cleanup sweep is driven by `ai-slop-cleaner`, an internal Ul
|
|
|
276
275
|
- The leader and a leader-spawned `executor` own all fixes; the cleaner reruns until zero blocking findings remain. Advisory findings live in the gate report only.
|
|
277
276
|
- Recursion guard: it must not spawn nested `ralplan`/`team`/`deep-interview`/`ultragoal`; broad or architectural findings are handed back to the leader as review blockers.
|
|
278
277
|
|
|
279
|
-
##
|
|
278
|
+
## Boundary completion cohort gate
|
|
280
279
|
|
|
281
|
-
|
|
280
|
+
The heavyweight gate runs **once per boundary generation**, not once per story and not once per review pass. Intermediate stories use the lightweight deferred gate above; this section applies at the boundary (the run's final required goal, or an explicit batch's final member).
|
|
282
281
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
282
|
+
One generation freezes the change set and reviews it exactly once:
|
|
283
|
+
|
|
284
|
+
1. Run implementation verification for the boundary's cumulative change set.
|
|
285
|
+
2. **Freeze the change set.** Compute one immutable `sourceHash` over the reviewed source. Every lane in this generation inspects that same frozen snapshot; a lane verdict carrying a different `sourceHash` is rejected.
|
|
286
|
+
3. **Run the cohort lanes on the frozen snapshot** — at most one `cleaner`, one `architect`, and one `qa` lane per generation. They may run in parallel because they share the frozen source; a second architect or QA lane in the same generation is rejected. The `cleaner` lane is the internal ai-slop-cleaner skill fragment run over the frozen change set: a read-only detector that emits an `AI SLOP CLEANUP REPORT`, and it still runs and records a passed/no-op report when there are no relevant edits. Its BLOCKING findings join the cohort findings rather than starting their own fix loop; advisory findings are included in the gate report only and are not written to the Ultragoal ledger.
|
|
286
287
|
4. Delegate an `architect` review covering all three lanes:
|
|
287
288
|
- architecture-side: system boundaries, layering, data/control flow, operational risks.
|
|
288
289
|
- product-side: user-visible behavior, acceptance criteria, edge cases, regressions.
|
|
@@ -295,17 +296,19 @@ An ultragoal story cannot be checkpointed `complete` until the active agent has
|
|
|
295
296
|
- API/package surfaces require a real artifact file or typed receipt whose artifact `kind` contains one of `api`, `package`, `consumer`, `black-box`, or `test-report`; examples: `api-package-test-report`, `package-consumer-report`, `black-box-api-receipt`. Algorithm/math surfaces require a real artifact file or typed receipt whose artifact `kind` contains one of `property`, `boundary`, `edge`, `adversarial`, `failure`, `math`, `algorithm`, or `test-report`; examples: `property-test-report`, `algorithm-boundary-report`. Bare `inlineEvidence` text alone is not sufficient for any surface.
|
|
296
297
|
- The mandatory **computer-use** red-team suite (`kill-switch-bypass`, `suspended-enforcement`, `permission-revoked`, …) is conditional, not universal: require it only when computer/desktop control is genuinely part of the product surface being dogfooded. For every other product type, prove the change through the matching live surface instead — browser-use automation for web/GUI, bash/CLI live invocation or argv replay for CLI, and real artifacts or typed receipts for API/package/algorithm/math. Editing docs, prompts, or skills that merely mention computer-use does not by itself make the computer-use suite applicable; pick the red-team surface that matches what the change actually ships.
|
|
297
298
|
7. The executor QA/red-team lane must report a matrix using `executorQa.contractCoverage`, `executorQa.surfaceEvidence`, `executorQa.adversarialCases`, and `executorQa.artifactRefs`. Not-applicable rows are allowed only in `contractCoverage` and `surfaceEvidence`; each `status: "not_applicable"` row requires `contractRef` plus `reason`. `adversarialCases` rows cannot be not-applicable.
|
|
298
|
-
8.
|
|
299
|
-
9. If
|
|
299
|
+
8. **Join before repairing.** Fold all three lane verdicts and the final code review into the strict gate under `iteration.reviewCohort` (`reviewGeneration`, `sourceHash`, `joined: true`, and the three `lanes`). No lane may checkpoint on its own, and no fix work starts until the findings are joined. Clean means `architectReview.architectureStatus`, `architectReview.productStatus`, and `architectReview.codeStatus` are all `"CLEAR"`, `architectReview.recommendation` is `"APPROVE"`, executor QA statuses are `"passed"`, iteration is `"passed"` with `fullRerun: true`, the cohort is joined with every lane clean and hash-bound, every evidence field is non-empty, every required matrix row is present, and every blockers array is empty. `COMMENT`, `WATCH`, `REQUEST CHANGES`, `BLOCK`, missing evidence, missing or shallow matrix rows, plan/code mismatches, or non-empty blockers are non-clean.
|
|
300
|
+
9. If the joined findings contain any blocker, do **not** checkpoint `complete` and do **not** call `goal({"op":"complete"})`. Record **one consolidated blocker batch** for all findings from the whole cohort instead of one story per lane:
|
|
300
301
|
```sh
|
|
301
|
-
gjc ultragoal record-review-blockers --goal-id <id> --title "Resolve verification blockers" --objective "<blocker-resolution objective>" --evidence "<
|
|
302
|
+
gjc ultragoal record-review-blockers --goal-id <id> --title "Resolve verification blockers" --objective "<blocker-resolution objective>" --evidence "<joined cohort findings>"
|
|
302
303
|
```
|
|
303
|
-
10.
|
|
304
|
-
11. Only after
|
|
304
|
+
10. One consolidated fix batch produces exactly **one new generation**. Re-freeze the fixed source as a new `sourceHash`, bump `reviewGeneration`, and set `deltaOnly: true` with `priorGenerationSourceHash` and the `deltaPaths` actually changed. Generation 2+ reviews are **delta-only**: they may not pull in unrelated scope without an explicit `scopeExpansion` carrying `severity`, `novelty`, and `justification`. Repeat until a generation joins clean.
|
|
305
|
+
11. Only after a generation joins clean, checkpoint the story as complete with a structured quality gate. The terminal critic runs **once** on that final joined generation; when `criticReview.sourceHash` is present it must match the cohort's `sourceHash`. The checkpoint creates a receipt in `ledger.jsonl`; `goals.json.status` alone is not proof. In aggregate mode, the final aggregate receipt must exist before the agent calls `goal({"op":"complete"})` to reconcile the inline UX goal state.
|
|
305
306
|
|
|
306
307
|
While an Ultragoal run is active, the `ask` tool is blocked for all agents. Record unresolved review decisions as durable blockers with `gjc ultragoal record-review-blockers` instead of prompting interactively.
|
|
307
308
|
|
|
308
|
-
The native `checkpoint --status complete` command rejects missing or shallow gates.
|
|
309
|
+
The native `checkpoint --status complete` command rejects missing or shallow gates, and reports **all** structural, evidence, surface, cohort, and declaration errors in one run rather than one per attempt. Each diagnostic carries a stable `path`, a stable machine-readable `code`, and a human `message`.
|
|
310
|
+
|
|
311
|
+
Validate before you checkpoint. `gjc ultragoal quality-gate validate --quality-gate-json <json-or-path> [--goal-id <id>] [--json]` applies exactly the same rules as `checkpoint --status complete` (including deferred-vs-boundary gate selection and artifact existence checks) but is strictly read-only: it never touches `goals.json`, `ledger.jsonl`, or goal state. It exits non-zero with the full diagnostics list when invalid, so authoring a gate is one pass instead of an edit/retry loop. `--quality-gate-json` must include:
|
|
309
312
|
|
|
310
313
|
```json
|
|
311
314
|
{
|
|
@@ -344,6 +347,16 @@ The native `checkpoint --status complete` command rejects missing or shallow gat
|
|
|
344
347
|
"evidence": "blockers absent or resolved and the full loop was rerun cleanly",
|
|
345
348
|
"fullRerun": true,
|
|
346
349
|
"rerunCommands": ["bun test:e2e", "bun test:red-team"],
|
|
350
|
+
"reviewCohort": {
|
|
351
|
+
"reviewGeneration": 1,
|
|
352
|
+
"sourceHash": "sha256:<frozen change-set hash every lane inspected>",
|
|
353
|
+
"joined": true,
|
|
354
|
+
"lanes": {
|
|
355
|
+
"cleaner": { "status": "passed", "sourceHash": "sha256:<same>", "evidence": "AI SLOP CLEANUP REPORT: zero blocking findings", "blockers": [] },
|
|
356
|
+
"architect": { "status": "CLEAR", "sourceHash": "sha256:<same>", "evidence": "architecture/product/code review of the frozen set", "blockers": [] },
|
|
357
|
+
"qa": { "status": "passed", "sourceHash": "sha256:<same>", "evidence": "e2e + red-team run against the frozen set", "blockers": [] }
|
|
358
|
+
}
|
|
359
|
+
},
|
|
347
360
|
"blockers": []
|
|
348
361
|
}
|
|
349
362
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Ultragoal Validation-Batch Contracts Fragment
|
|
2
|
+
|
|
3
|
+
Internal Ultragoal sub-skill fragment (`kind: "skill-fragment"`, parent skill `ultragoal`, installed at `skill-fragments/ultragoal/validation-batch-contracts.md`). The Ultragoal leader loads it on demand before checkpointing a validation-batch member; it is never user-facing, not slash-command discoverable, and never resolvable through `skill://`. The runtime enforces every rule below verbatim and fails closed.
|
|
4
|
+
|
|
5
|
+
## Validation-batch checkpoint contract
|
|
6
|
+
|
|
7
|
+
- **Non-final members** checkpoint `complete` with a single top-level `deferredToBatch` quality gate (kind `validation-batch-deferred`): a passing `targetedVerification` lane plus a cumulative-since-base change set. An ai-slop-cleaner pass and a full verification rerun are boundary duties and are optional here; when either is supplied it must pass and be blocker-free. A `deferredToBatch` gate must NOT contain `architectReview`, `executorQa`, or `validationBatchClose` — deferring never manufactures fake review approvals.
|
|
8
|
+
- **Lane declaration is fail-closed.** `deferredToBatch.ranLanes` declares which lanes actually ran. A declared lane whose evidence is missing is rejected, evidence for an undeclared lane is rejected, `targetedVerification` must always be declared when `ranLanes` is present, and `ranLanes` can never declare `architectReview` or `executorQa`. Omitting `ranLanes` is allowed only for a gate that carries the mandatory targeted lane alone.
|
|
9
|
+
- **The final member** (`finalGoalId`) checkpoints `complete` with the normal full strict gate PLUS a top-level `validationBatchClose` proof that covers all member IDs, member metadata hashes, member receipt/checkpoint-ledger-event IDs, per-member change-set hashes, and union change-set coverage. The final close only starts once every non-final member is already `complete` with a structurally fresh deferred receipt (out-of-order close is rejected).
|
|
10
|
+
- Close state is append-only proof: it lives in the final member's checkpoint receipt and matching `goal_checkpointed` ledger row only. Never stamp `closedReceiptId`/`closedAt` or any close-state field onto member goals, and never append a separate close ledger event.
|
|
11
|
+
- Change sets are cumulative-since-base: each member's `changeSet.paths` is the whole-worktree diff vs base (`cumulativeFromBase: true`), `memberGoalId` is a label not a per-path attribution, and `unionChangeSet.paths` carries no per-goal attribution.
|
|
12
|
+
- Batch invalidation is fail-closed: steering mutations that would invalidate a batch are rejected while any member holds a fresh deferred receipt.
|
|
@@ -9,10 +9,10 @@ import deepInterviewSkill from "./gjc/skills/deep-interview/SKILL.md" with { typ
|
|
|
9
9
|
import ralplanSkill from "./gjc/skills/ralplan/SKILL.md" with { type: "text" };
|
|
10
10
|
import teamSkill from "./gjc/skills/team/SKILL.md" with { type: "text" };
|
|
11
11
|
import aiSlopCleanerFragment from "./gjc/skills/ultragoal/ai-slop-cleaner.md" with { type: "text" };
|
|
12
|
-
import
|
|
12
|
+
import ultragoalSkill from "./gjc/skills/ultragoal/SKILL.md" with { type: "text" };
|
|
13
|
+
import validationBatchContractsFragment from "./gjc/skills/ultragoal/validation-batch-contracts.md" with {
|
|
13
14
|
type: "text",
|
|
14
15
|
};
|
|
15
|
-
import ultragoalSkill from "./gjc/skills/ultragoal/SKILL.md" with { type: "text" };
|
|
16
16
|
|
|
17
17
|
export const DEFAULT_GJC_DEFINITION_NAMES = ["deep-interview", "ralplan", "team", "ultragoal"] as const;
|
|
18
18
|
export type DefaultGjcDefinitionName = (typeof DEFAULT_GJC_DEFINITION_NAMES)[number];
|
|
@@ -119,8 +119,8 @@ const DEFAULT_GJC_DEFINITIONS: readonly DefaultGjcDefinition[] = [
|
|
|
119
119
|
{
|
|
120
120
|
kind: "skill-fragment",
|
|
121
121
|
parentSkillName: "ultragoal",
|
|
122
|
-
relativePath: "skill-fragments/ultragoal/
|
|
123
|
-
content:
|
|
122
|
+
relativePath: "skill-fragments/ultragoal/validation-batch-contracts.md",
|
|
123
|
+
content: validationBatchContractsFragment,
|
|
124
124
|
},
|
|
125
125
|
];
|
|
126
126
|
|
package/src/exa/mcp-client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TSchema } from "@gajae-code/ai";
|
|
2
|
-
import { $
|
|
2
|
+
import { $credentialEnv, logger } from "@gajae-code/utils";
|
|
3
3
|
import type { CustomTool, CustomToolResult } from "../extensibility/custom-tools/types";
|
|
4
4
|
import { callMCP } from "../runtime-mcp/json-rpc";
|
|
5
5
|
import type {
|
|
@@ -11,9 +11,18 @@ import type {
|
|
|
11
11
|
MCPToolWrapperConfig,
|
|
12
12
|
} from "./types";
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Find `EXA_API_KEY` from trusted environment sources.
|
|
16
|
+
*
|
|
17
|
+
* The key authenticates every Exa MCP call and travels in the request URL, so
|
|
18
|
+
* whatever can set it decides which account the agent's searches run through —
|
|
19
|
+
* and therefore who can see those queries. `$env` merges the caller's `cwd/.env`
|
|
20
|
+
* into `process.env`, so reading it there would let repository content supply
|
|
21
|
+
* that account. Resolve it the same way provider credentials are: launching
|
|
22
|
+
* shell plus GJC/user-owned `.env` files, never the project `.env`.
|
|
23
|
+
*/
|
|
15
24
|
export function findApiKey(): string | null {
|
|
16
|
-
return $
|
|
25
|
+
return $credentialEnv("EXA_API_KEY") ?? null;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
@@ -8,12 +8,125 @@ import { executeShell, type MinimizerOptions, Shell } from "@gajae-code/natives"
|
|
|
8
8
|
import { postmortem } from "@gajae-code/utils";
|
|
9
9
|
import { Settings, type ShellMinimizerSettings } from "../config/settings";
|
|
10
10
|
import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_ARTIFACT_MAX_BYTES,
|
|
13
|
+
DEFAULT_MAX_BYTES,
|
|
14
|
+
OutputSink,
|
|
15
|
+
type TerminalArtifactPublisher,
|
|
16
|
+
truncateHeadBytes,
|
|
17
|
+
} from "../session/streaming-output";
|
|
18
|
+
import { formatArtifactReference, resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
|
|
13
19
|
import { getOrCreateSnapshot } from "../utils/shell-snapshot";
|
|
14
20
|
import { NON_INTERACTIVE_ENV } from "./non-interactive-env";
|
|
15
21
|
|
|
22
|
+
export interface BashArtifactSaveSummary {
|
|
23
|
+
artifactId: string;
|
|
24
|
+
complete: boolean;
|
|
25
|
+
omittedBytes?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type BashMinimizedSaveReturn = BashArtifactSaveResult | BashArtifactSaveSummary | string | undefined;
|
|
29
|
+
|
|
30
|
+
export type BashArtifactSaveResult =
|
|
31
|
+
| { status: "saved"; artifactId: string; complete: true; omittedBytes?: undefined }
|
|
32
|
+
| { status: "saved"; artifactId: string; complete: false; omittedBytes: number }
|
|
33
|
+
| { status: "unavailable" }
|
|
34
|
+
| { status: "failed"; diagnostic: string };
|
|
35
|
+
|
|
36
|
+
function summarizeLegacyArtifactSave(artifactId: string, originalText: string): BashArtifactSaveResult {
|
|
37
|
+
const inputBytes = Buffer.byteLength(originalText, "utf-8");
|
|
38
|
+
if (inputBytes <= DEFAULT_ARTIFACT_MAX_BYTES) {
|
|
39
|
+
return { status: "saved", artifactId, complete: true };
|
|
40
|
+
}
|
|
41
|
+
const retainedBytes = truncateHeadBytes(originalText, DEFAULT_ARTIFACT_MAX_BYTES).bytes;
|
|
42
|
+
return {
|
|
43
|
+
status: "saved",
|
|
44
|
+
artifactId,
|
|
45
|
+
complete: false,
|
|
46
|
+
omittedBytes: inputBytes - retainedBytes,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeExplicitSavedArtifact(
|
|
51
|
+
artifactId: string,
|
|
52
|
+
complete: boolean,
|
|
53
|
+
omittedBytes: number | undefined,
|
|
54
|
+
): BashArtifactSaveResult {
|
|
55
|
+
if (complete) {
|
|
56
|
+
return (omittedBytes ?? 0) > 0
|
|
57
|
+
? { status: "failed", diagnostic: "artifact save reported complete output with omitted bytes" }
|
|
58
|
+
: { status: "saved", artifactId, complete: true };
|
|
59
|
+
}
|
|
60
|
+
return typeof omittedBytes === "number" && omittedBytes > 0
|
|
61
|
+
? { status: "saved", artifactId, complete: false, omittedBytes }
|
|
62
|
+
: { status: "failed", diagnostic: "artifact save reported incomplete output without omitted bytes" };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeMinimizedSaveResult(value: BashMinimizedSaveReturn, originalText: string): BashArtifactSaveResult {
|
|
66
|
+
if (typeof value === "string") return summarizeLegacyArtifactSave(value, originalText);
|
|
67
|
+
if (!value) return { status: "unavailable" };
|
|
68
|
+
if (!("status" in value)) {
|
|
69
|
+
return normalizeExplicitSavedArtifact(value.artifactId, value.complete, value.omittedBytes);
|
|
70
|
+
}
|
|
71
|
+
if (value.status !== "saved") return value;
|
|
72
|
+
return normalizeExplicitSavedArtifact(value.artifactId, value.complete, value.omittedBytes);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function normalizeMinimizedSaveResultForTests(
|
|
76
|
+
value: BashMinimizedSaveReturn,
|
|
77
|
+
originalText: string,
|
|
78
|
+
): BashArtifactSaveResult {
|
|
79
|
+
return normalizeMinimizedSaveResult(value, originalText);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function completeRawArtifactAvailable(summary: {
|
|
83
|
+
artifactId?: string;
|
|
84
|
+
artifactTruncatedBytes?: number;
|
|
85
|
+
artifactFailureDiagnostic?: string;
|
|
86
|
+
}): boolean {
|
|
87
|
+
return (
|
|
88
|
+
summary.artifactId !== undefined &&
|
|
89
|
+
(summary.artifactTruncatedBytes ?? 0) <= 0 &&
|
|
90
|
+
summary.artifactFailureDiagnostic === undefined
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function appendModelNotice(output: string, notice: string): string {
|
|
95
|
+
const separator = output.length > 0 && !output.endsWith("\n") ? "\n" : "";
|
|
96
|
+
return `${output}${separator}${notice}\n`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function minimizedSaveNotice(
|
|
100
|
+
result: BashArtifactSaveResult,
|
|
101
|
+
summary: { artifactId?: string; artifactTruncatedBytes?: number; artifactFailureDiagnostic?: string },
|
|
102
|
+
): string | undefined {
|
|
103
|
+
if (result.status === "failed") return `Bash output artifact save failed: ${result.diagnostic}`;
|
|
104
|
+
if (result.status === "unavailable" && !completeRawArtifactAvailable(summary)) {
|
|
105
|
+
return "Bash output artifact unavailable: full original output could not be stored because artifact storage is unavailable.";
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function minimizedArtifactFooter(result: Extract<BashArtifactSaveResult, { status: "saved" }>): string {
|
|
111
|
+
const reference = result.complete
|
|
112
|
+
? `artifact://${result.artifactId}`
|
|
113
|
+
: formatArtifactReference(result.artifactId, result.omittedBytes);
|
|
114
|
+
return `[raw output: ${reference}]`;
|
|
115
|
+
}
|
|
116
|
+
|
|
16
117
|
export interface BashExecutorOptions {
|
|
118
|
+
/**
|
|
119
|
+
* Invoked when the native minimizer rewrote the command's output, giving
|
|
120
|
+
* the caller a chance to persist the lossless original capture (typically
|
|
121
|
+
* via the session's `ArtifactManager`). Complete saves preserve the
|
|
122
|
+
* historical `[raw output: artifact://<id>]` footer; capped saves carry an
|
|
123
|
+
* honest retained/omitted reference. A legacy string id is still accepted
|
|
124
|
+
* for non-tool callers and is classified from the original UTF-8 byte count.
|
|
125
|
+
*/
|
|
126
|
+
onMinimizedSave?: (
|
|
127
|
+
originalText: string,
|
|
128
|
+
info: { filter: string; inputBytes: number; outputBytes: number },
|
|
129
|
+
) => Promise<BashMinimizedSaveReturn>;
|
|
17
130
|
cwd?: string;
|
|
18
131
|
timeout?: number | null;
|
|
19
132
|
onChunk?: (chunk: string) => void;
|
|
@@ -32,23 +145,18 @@ export interface BashExecutorOptions {
|
|
|
32
145
|
/** Artifact path/id for full output storage */
|
|
33
146
|
artifactPath?: string;
|
|
34
147
|
artifactId?: string;
|
|
148
|
+
/** Optional terminal publisher for managed artifacts without writable paths. */
|
|
149
|
+
artifactPublisher?: TerminalArtifactPublisher;
|
|
150
|
+
/** Optional Bash-specific retained tail budget in bytes. */
|
|
151
|
+
spillThreshold?: number;
|
|
152
|
+
/** Optional Bash-specific retained head budget in bytes. */
|
|
153
|
+
headBytes?: number;
|
|
35
154
|
/** Execute without retaining a native Shell in the persistent session registry. */
|
|
36
155
|
oneShot?: boolean;
|
|
37
156
|
/** Ignore user-configured shell command prefixes. Used by constrained read-only shells. */
|
|
38
157
|
ignoreShellPrefix?: boolean;
|
|
39
158
|
/** Skip sourced shell snapshots. Used by constrained read-only shells. */
|
|
40
159
|
disableShellSnapshot?: boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Invoked when the native minimizer rewrote the command's output, giving
|
|
43
|
-
* the caller a chance to persist the lossless original capture (typically
|
|
44
|
-
* via the session's `ArtifactManager`). The returned id is spliced into
|
|
45
|
-
* the sink output as `artifact://<id>` so the agent can retrieve the raw
|
|
46
|
-
* bytes. Return `undefined` to skip the footer.
|
|
47
|
-
*/
|
|
48
|
-
onMinimizedSave?: (
|
|
49
|
-
originalText: string,
|
|
50
|
-
info: { filter: string; inputBytes: number; outputBytes: number },
|
|
51
|
-
) => Promise<string | undefined>;
|
|
52
160
|
}
|
|
53
161
|
|
|
54
162
|
export interface BashResult {
|
|
@@ -61,6 +169,8 @@ export interface BashResult {
|
|
|
61
169
|
outputLines: number;
|
|
62
170
|
outputBytes: number;
|
|
63
171
|
artifactId?: string;
|
|
172
|
+
artifactTruncatedBytes?: number;
|
|
173
|
+
artifactFailureDiagnostic?: string;
|
|
64
174
|
}
|
|
65
175
|
|
|
66
176
|
const shellSessions = new Map<string, Shell>();
|
|
@@ -144,7 +254,9 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
144
254
|
onRawChunk: options?.onRawChunk,
|
|
145
255
|
artifactPath: options?.artifactPath,
|
|
146
256
|
artifactId: options?.artifactId,
|
|
147
|
-
|
|
257
|
+
artifactPublisher: options?.artifactPublisher,
|
|
258
|
+
spillThreshold: options?.spillThreshold ?? DEFAULT_MAX_BYTES,
|
|
259
|
+
headBytes: options?.headBytes ?? resolveOutputSinkHeadBytes(settings),
|
|
148
260
|
maxColumns: resolveOutputMaxColumns(settings),
|
|
149
261
|
// Throttle the streaming preview callback to avoid saturating the
|
|
150
262
|
// event loop when commands produce massive output (e.g. seq 1 50M).
|
|
@@ -332,21 +444,23 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
332
444
|
|
|
333
445
|
// When the native minimizer rewrote the output, swap the sink's accumulated
|
|
334
446
|
// raw stream for the minimized text, persist the original as a session
|
|
335
|
-
// artifact, and splice an
|
|
336
|
-
//
|
|
447
|
+
// artifact, and splice an artifact footer into the visible text so the agent
|
|
448
|
+
// can retrieve retained raw bytes without a false completeness claim.
|
|
337
449
|
const minimized = winner.result.minimized;
|
|
450
|
+
let minimizedSaveResult: BashArtifactSaveResult | undefined;
|
|
338
451
|
if (minimized && minimized.text !== minimized.originalText) {
|
|
339
452
|
sink.replace(minimized.text);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
453
|
+
const saved = options?.onMinimizedSave
|
|
454
|
+
? await options.onMinimizedSave(minimized.originalText, {
|
|
455
|
+
filter: minimized.filter,
|
|
456
|
+
inputBytes: minimized.inputBytes,
|
|
457
|
+
outputBytes: minimized.outputBytes,
|
|
458
|
+
})
|
|
459
|
+
: undefined;
|
|
460
|
+
minimizedSaveResult = normalizeMinimizedSaveResult(saved, minimized.originalText);
|
|
461
|
+
if (minimizedSaveResult.status === "saved") {
|
|
462
|
+
const sep = minimized.text.endsWith("\n") ? "" : "\n";
|
|
463
|
+
sink.push(`${sep}${minimizedArtifactFooter(minimizedSaveResult)}\n`);
|
|
350
464
|
}
|
|
351
465
|
}
|
|
352
466
|
|
|
@@ -366,10 +480,13 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
366
480
|
}
|
|
367
481
|
|
|
368
482
|
// Normal completion
|
|
483
|
+
const summary = await sink.dump();
|
|
484
|
+
const saveNotice = minimizedSaveResult ? minimizedSaveNotice(minimizedSaveResult, summary) : undefined;
|
|
369
485
|
return {
|
|
370
486
|
exitCode: winner.result.exitCode,
|
|
371
487
|
cancelled: false,
|
|
372
|
-
...
|
|
488
|
+
...summary,
|
|
489
|
+
...(saveNotice ? { output: appendModelNotice(summary.output, saveNotice) } : {}),
|
|
373
490
|
};
|
|
374
491
|
} catch (err) {
|
|
375
492
|
resetSession = true;
|
|
@@ -179,8 +179,12 @@ export class ExtensionRunner {
|
|
|
179
179
|
|
|
180
180
|
#getModel: () => Model | undefined = () => undefined;
|
|
181
181
|
#isIdleFn: () => boolean = () => true;
|
|
182
|
+
#getActivePromptHandleFn: () => string | undefined = () => undefined;
|
|
182
183
|
#waitForIdleFn: () => Promise<void> = async () => {};
|
|
183
184
|
#abortFn: () => void = () => {};
|
|
185
|
+
#abortPromptAndWaitFn: NonNullable<ExtensionContextActions["abortPromptAndWait"]> = async () => {
|
|
186
|
+
throw new Error("abortPromptAndWait binding is unavailable");
|
|
187
|
+
};
|
|
184
188
|
#hasPendingMessagesFn: () => boolean = () => false;
|
|
185
189
|
#getPendingMessageCountsFn: () => { steering: number; followUp: number; nextTurn: number } = () => ({
|
|
186
190
|
steering: 0,
|
|
@@ -214,6 +218,7 @@ export class ExtensionRunner {
|
|
|
214
218
|
#getJobsFn: ExtensionContextActions["getJobs"] = undefined;
|
|
215
219
|
#sdkControlFn: ExtensionContextActions["sdkControl"] = undefined;
|
|
216
220
|
#setSdkPermissionProviderFn: ExtensionContextActions["setSdkPermissionProvider"] = undefined;
|
|
221
|
+
#setSdkClientBridgeFn: ExtensionContextActions["setSdkClientBridge"] = undefined;
|
|
217
222
|
|
|
218
223
|
#invokeSkillFn: ExtensionContextActions["invokeSkill"] = undefined;
|
|
219
224
|
#setPlanModeFn: ExtensionContextActions["setPlanMode"] = undefined;
|
|
@@ -297,7 +302,13 @@ export class ExtensionRunner {
|
|
|
297
302
|
// Context actions (required)
|
|
298
303
|
this.#getModel = contextActions.getModel;
|
|
299
304
|
this.#isIdleFn = contextActions.isIdle;
|
|
305
|
+
this.#getActivePromptHandleFn = contextActions.getActivePromptHandle ?? (() => undefined);
|
|
300
306
|
this.#abortFn = contextActions.abort;
|
|
307
|
+
this.#abortPromptAndWaitFn =
|
|
308
|
+
contextActions.abortPromptAndWait ??
|
|
309
|
+
(async () => {
|
|
310
|
+
throw new Error("abortPromptAndWait binding is unavailable");
|
|
311
|
+
});
|
|
301
312
|
this.#hasPendingMessagesFn = contextActions.hasPendingMessages;
|
|
302
313
|
this.#getPendingMessageCountsFn =
|
|
303
314
|
contextActions.getPendingMessageCounts ?? (() => ({ steering: 0, followUp: 0, nextTurn: 0 }));
|
|
@@ -331,6 +342,7 @@ export class ExtensionRunner {
|
|
|
331
342
|
this.#getJobsFn = contextActions.getJobs;
|
|
332
343
|
this.#sdkControlFn = contextActions.sdkControl;
|
|
333
344
|
this.#setSdkPermissionProviderFn = contextActions.setSdkPermissionProvider;
|
|
345
|
+
this.#setSdkClientBridgeFn = contextActions.setSdkClientBridge;
|
|
334
346
|
|
|
335
347
|
// Command context actions (optional, only for interactive mode)
|
|
336
348
|
if (commandContextActions) {
|
|
@@ -551,8 +563,10 @@ export class ExtensionRunner {
|
|
|
551
563
|
get model() {
|
|
552
564
|
return getModel();
|
|
553
565
|
},
|
|
566
|
+
getActivePromptHandle: () => this.#getActivePromptHandleFn(),
|
|
554
567
|
isIdle: () => this.#isIdleFn(),
|
|
555
568
|
abort: () => this.#abortFn(),
|
|
569
|
+
abortPromptAndWait: (handle, options) => this.#abortPromptAndWaitFn(handle, options),
|
|
556
570
|
hasPendingMessages: () => this.#hasPendingMessagesFn(),
|
|
557
571
|
getPendingMessageCounts: () => this.#getPendingMessageCountsFn(),
|
|
558
572
|
getTranscript: () => this.#getTranscriptFn(),
|
|
@@ -582,6 +596,7 @@ export class ExtensionRunner {
|
|
|
582
596
|
getJobs: () => this.#getJobsFn?.(),
|
|
583
597
|
sdkControl: (operation, input) => this.#sdkControlFn?.(operation, input),
|
|
584
598
|
setSdkPermissionProvider: provider => this.#setSdkPermissionProviderFn?.(provider),
|
|
599
|
+
setSdkClientBridge: bridge => this.#setSdkClientBridgeFn?.(bridge),
|
|
585
600
|
sdkBindings: () => [
|
|
586
601
|
...(this.#cycleModelFn ? ["cycleModel"] : []),
|
|
587
602
|
...(this.#setModelProfileFn ? ["setModelProfile"] : []),
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
* - Interact with the user via UI primitives
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
AgentMessage,
|
|
13
|
+
AgentToolResult,
|
|
14
|
+
AgentToolUpdateCallback,
|
|
15
|
+
RunSettlementProof,
|
|
16
|
+
ThinkingLevel,
|
|
17
|
+
} from "@gajae-code/agent-core";
|
|
12
18
|
import type { CompactionResult } from "@gajae-code/agent-core/compaction";
|
|
13
19
|
import type {
|
|
14
20
|
Api,
|
|
@@ -38,6 +44,7 @@ import type { CustomEditor } from "../../modes/components/custom-editor";
|
|
|
38
44
|
import type { WorkflowGateEmitter } from "../../modes/shared/agent-wire/workflow-gate-broker";
|
|
39
45
|
import type { Theme } from "../../modes/theme/theme";
|
|
40
46
|
import type {
|
|
47
|
+
ClientBridge,
|
|
41
48
|
ClientBridgePermissionOption,
|
|
42
49
|
ClientBridgePermissionOutcome,
|
|
43
50
|
ClientBridgePermissionToolCall,
|
|
@@ -355,8 +362,12 @@ export interface ExtensionContext {
|
|
|
355
362
|
model: Model | undefined;
|
|
356
363
|
/** Whether the agent is idle (not streaming) */
|
|
357
364
|
isIdle(): boolean;
|
|
365
|
+
/** Stable resource ownership identifier for the active prompt run. */
|
|
366
|
+
getActivePromptHandle(): string | undefined;
|
|
358
367
|
/** Abort the current agent operation */
|
|
359
368
|
abort(): void;
|
|
369
|
+
/** Abort and prove whether resources for a specific prompt settled. */
|
|
370
|
+
abortPromptAndWait?(handle: string, options: { graceMs: number }): Promise<RunSettlementProof>;
|
|
360
371
|
/** Whether there are queued messages waiting */
|
|
361
372
|
hasPendingMessages(): boolean;
|
|
362
373
|
/** Typed pending-message counts per queue (steering, follow-up, next-turn). */
|
|
@@ -416,6 +427,8 @@ export interface ExtensionContext {
|
|
|
416
427
|
) => Promise<ClientBridgePermissionOutcome>)
|
|
417
428
|
| undefined,
|
|
418
429
|
): void;
|
|
430
|
+
/** Install a client bridge backed by a live SDK reverse provider lease. */
|
|
431
|
+
setSdkClientBridge?(bridge: ClientBridge | undefined): void;
|
|
419
432
|
/** Names of session SDK seams actually installed by the active runtime. */
|
|
420
433
|
sdkBindings?(): readonly string[];
|
|
421
434
|
|
|
@@ -1436,7 +1449,10 @@ export interface ExtensionActions {
|
|
|
1436
1449
|
export interface ExtensionContextActions {
|
|
1437
1450
|
getModel: () => Model | undefined;
|
|
1438
1451
|
isIdle: () => boolean;
|
|
1452
|
+
/** Stable resource ownership identifier for the active prompt run. */
|
|
1453
|
+
getActivePromptHandle?: () => string | undefined;
|
|
1439
1454
|
abort: () => void;
|
|
1455
|
+
abortPromptAndWait?: (handle: string, options: { graceMs: number }) => Promise<RunSettlementProof>;
|
|
1440
1456
|
hasPendingMessages: () => boolean;
|
|
1441
1457
|
/** Typed pending-message counts per queue; optional for embedders without a counted queue. */
|
|
1442
1458
|
getPendingMessageCounts?: () => { steering: number; followUp: number; nextTurn: number };
|
|
@@ -1484,6 +1500,7 @@ export interface ExtensionContextActions {
|
|
|
1484
1500
|
) => Promise<ClientBridgePermissionOutcome>)
|
|
1485
1501
|
| undefined,
|
|
1486
1502
|
) => void;
|
|
1503
|
+
setSdkClientBridge?: (bridge: ClientBridge | undefined) => void;
|
|
1487
1504
|
sdkControl?: (operation: string, input: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
1488
1505
|
invokeSkill?: (
|
|
1489
1506
|
name: string,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { logger } from "@gajae-code/utils";
|
|
2
|
-
|
|
2
|
+
import { bundleIdentity } from "./lifecycle-reconciliation";
|
|
3
3
|
import { loadEffectiveGjcPluginRegistry } from "./registry";
|
|
4
4
|
import { type SessionQuarantine, validateSessionBundles, verifyEntryHashes } from "./session-validation";
|
|
5
|
-
import { GjcPluginLoadError, type GjcPluginRegistryEntry } from "./types";
|
|
5
|
+
import { GjcPluginLoadError, type GjcPluginRegistryEntry, type GjcPluginScope } from "./types";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Constrained plugin-hook loader.
|
|
@@ -37,6 +37,7 @@ const DENIED_API_METHODS = [
|
|
|
37
37
|
|
|
38
38
|
interface DeclaredHook {
|
|
39
39
|
plugin: string;
|
|
40
|
+
scope: GjcPluginScope;
|
|
40
41
|
event: string;
|
|
41
42
|
target?: string;
|
|
42
43
|
phase?: "before" | "after";
|
|
@@ -52,6 +53,7 @@ function collectDeclaredHooks(entries: readonly GjcPluginRegistryEntry[]): Decla
|
|
|
52
53
|
if (disabled.has(h.extensionId)) continue;
|
|
53
54
|
out.push({
|
|
54
55
|
plugin: entry.name,
|
|
56
|
+
scope: entry.scope,
|
|
55
57
|
event: h.event,
|
|
56
58
|
target: h.target,
|
|
57
59
|
phase: h.phase,
|
|
@@ -88,6 +90,7 @@ async function loadOneHook(
|
|
|
88
90
|
return {
|
|
89
91
|
hook: null,
|
|
90
92
|
quarantine: {
|
|
93
|
+
identity: bundleIdentity(declared.scope, declared.plugin),
|
|
91
94
|
plugin: declared.plugin,
|
|
92
95
|
surfaceId: `hook:${declared.event}:${declared.target ?? ""}`,
|
|
93
96
|
code: "invalid_hook",
|
|
@@ -99,6 +102,7 @@ async function loadOneHook(
|
|
|
99
102
|
return {
|
|
100
103
|
hook: null,
|
|
101
104
|
quarantine: {
|
|
105
|
+
identity: bundleIdentity(declared.scope, declared.plugin),
|
|
102
106
|
plugin: declared.plugin,
|
|
103
107
|
surfaceId: `hook:${declared.event}`,
|
|
104
108
|
code: "invalid_hook",
|
|
@@ -113,6 +117,7 @@ async function loadOneHook(
|
|
|
113
117
|
return {
|
|
114
118
|
hook: null,
|
|
115
119
|
quarantine: {
|
|
120
|
+
identity: bundleIdentity(declared.scope, declared.plugin),
|
|
116
121
|
plugin: declared.plugin,
|
|
117
122
|
surfaceId: `hook:${declared.event}`,
|
|
118
123
|
code,
|
|
@@ -125,6 +130,7 @@ async function loadOneHook(
|
|
|
125
130
|
return {
|
|
126
131
|
hook: null,
|
|
127
132
|
quarantine: {
|
|
133
|
+
identity: bundleIdentity(declared.scope, declared.plugin),
|
|
128
134
|
plugin: declared.plugin,
|
|
129
135
|
surfaceId: `hook:${declared.event}`,
|
|
130
136
|
code: "runtime_mismatch",
|