berry-agent 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* goal 服务面(03 §10.5 + 04 §12 调度条 goal 挂钟节)。
|
|
3
|
+
*
|
|
4
|
+
* 编舞总览:
|
|
5
|
+
* - 生命周期:activate(单 active 撞席守卫 + 挂钟注册)/ resume(重绑锚 +
|
|
6
|
+
* 复活挂钟)/ complete(完成否决律机器面:open 项在或 gate 未全绿即拒)/
|
|
7
|
+
* abandon(终态同笔停摆);
|
|
8
|
+
* - 续跑触发:wake 双道(clock/manual)——重绑护栏(关闭后旧唤醒不落地)+
|
|
9
|
+
* wakeGate 双帽(停滞判定帽 + 唤醒预算帽 §4)+ 停滞硬停(连续无进展即
|
|
10
|
+
* 停摆报告)+ 归因轮身份 durable 落账(goal_wakes 表——跨进程可审计);
|
|
11
|
+
* - 预算双轨(04 §5):记账刹停腿(recordTurn 前台计数 + foldDelegation
|
|
12
|
+
* 委派结算折叠——两腿先到先刹)+ budgetExceeded 复验面(agent_pre_step
|
|
13
|
+
* 消费——防记账腿与执行腿竞速漏刹);
|
|
14
|
+
* - 挂钟行管理:GoalJobsFace 窄面单漏斗 + 第五槽迟到注入(先 goal 后
|
|
15
|
+
* scheduler 装载序的补接线——effect 可逆双序对称)。
|
|
16
|
+
*/
|
|
17
|
+
import { randomUUID } from 'node:crypto';
|
|
18
|
+
import { BaseError } from '../contracts/index.js';
|
|
19
|
+
import { foldGoalTodos, openGoalItems, progressFingerprint } from './fold.js';
|
|
20
|
+
import { evaluateGoalGates } from './gates.js';
|
|
21
|
+
/** objective 硬帽(16KiB——与 scheduler prompt 帽同值) */
|
|
22
|
+
const OBJECTIVE_MAX_BYTES = 16 * 1024;
|
|
23
|
+
/** 停滞判定帽缺省(连续无进展唤醒轮数——超过即硬停报告) */
|
|
24
|
+
export const DEFAULT_STALL_LIMIT = 5;
|
|
25
|
+
/** 唤醒预算帽缺省(§4 maxConsecutiveWakes 同值——连续 clock 唤醒无进展即拒) */
|
|
26
|
+
export const DEFAULT_WAKE_BUDGET_LIMIT = 3;
|
|
27
|
+
/** 轮间沉淀摘要帽(04 §3.7 complete 单发 maxChars——注入面单条消息量级) */
|
|
28
|
+
export const GOAL_DEPOSIT_MAX_CHARS = 2000;
|
|
29
|
+
/** 沉淀单发提示词(objective + 计划态两段——单发即弃零会话态) */
|
|
30
|
+
function depositPrompt(row, items) {
|
|
31
|
+
const completed = items.filter((item) => item.status === 'completed').length;
|
|
32
|
+
return [
|
|
33
|
+
'为长期目标的下一轮工作生成一段简短沉淀摘要(供下一轮对话开局注入)。',
|
|
34
|
+
`目标:${row.objective}`,
|
|
35
|
+
`当前计划态:open ${items.length - completed} 项 / completed ${completed} 项。`,
|
|
36
|
+
'要求:概括目标、当前进度与下一步建议,不含多余客套。',
|
|
37
|
+
].join('\n');
|
|
38
|
+
}
|
|
39
|
+
/** goals 表行查列清单(单源——get/list/activeFor 共用) */
|
|
40
|
+
const GOAL_COLUMNS = `id, session_id, objective, status, activated_seq, schedule, prompt_snapshot,
|
|
41
|
+
needs_write, write_approved, budget_messages_cap, budget_messages_used, budget_folded_units,
|
|
42
|
+
stall_streak, wake_streak, last_fingerprint, created_at, updated_at,
|
|
43
|
+
ended_at, ending_note`;
|
|
44
|
+
class GoalDao {
|
|
45
|
+
db;
|
|
46
|
+
constructor(db) {
|
|
47
|
+
this.db = db;
|
|
48
|
+
}
|
|
49
|
+
get(id) {
|
|
50
|
+
const row = this.db.prepare(`SELECT ${GOAL_COLUMNS} FROM goals WHERE id = ?`).get(id);
|
|
51
|
+
return row ? goalFromDb(row) : undefined;
|
|
52
|
+
}
|
|
53
|
+
activeFor(sessionId) {
|
|
54
|
+
const row = this.db
|
|
55
|
+
.prepare(`SELECT ${GOAL_COLUMNS} FROM goals WHERE session_id = ? AND status = 'active' LIMIT 1`)
|
|
56
|
+
.get(sessionId);
|
|
57
|
+
return row ? goalFromDb(row) : undefined;
|
|
58
|
+
}
|
|
59
|
+
list() {
|
|
60
|
+
const rows = this.db.prepare(`SELECT ${GOAL_COLUMNS} FROM goals ORDER BY created_at DESC`).all();
|
|
61
|
+
return rows.map(goalFromDb);
|
|
62
|
+
}
|
|
63
|
+
insert(goal) {
|
|
64
|
+
this.db
|
|
65
|
+
.prepare(`INSERT INTO goals (id, session_id, objective, status, activated_seq, schedule, prompt_snapshot,
|
|
66
|
+
needs_write, write_approved, budget_messages_cap, budget_messages_used, budget_folded_units,
|
|
67
|
+
stall_streak, wake_streak, last_fingerprint, created_at, updated_at,
|
|
68
|
+
ended_at, ending_note)
|
|
69
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
70
|
+
.run(goal.id, goal.sessionId, goal.objective, goal.status, goal.activatedSeq, goal.schedule, goal.promptSnapshot, goal.needsWrite ? 1 : 0, goal.writeApproved ? 1 : 0, goal.budgetMessagesCap, goal.budgetMessagesUsed, goal.budgetFoldedUnits, goal.stallStreak, goal.wakeStreak, goal.lastFingerprint, goal.createdAt, goal.updatedAt, goal.endedAt, goal.endingNote);
|
|
71
|
+
}
|
|
72
|
+
/** 行更新(patch 形——updatedAt 调用方供) */
|
|
73
|
+
update(id, patch, updatedAt) {
|
|
74
|
+
const current = this.get(id);
|
|
75
|
+
if (!current)
|
|
76
|
+
return;
|
|
77
|
+
const next = { ...current, ...patch, updatedAt };
|
|
78
|
+
this.db
|
|
79
|
+
.prepare(`UPDATE goals SET session_id = ?, objective = ?, status = ?, activated_seq = ?, schedule = ?,
|
|
80
|
+
prompt_snapshot = ?, needs_write = ?, write_approved = ?, budget_messages_cap = ?,
|
|
81
|
+
budget_messages_used = ?, budget_folded_units = ?, stall_streak = ?,
|
|
82
|
+
wake_streak = ?, last_fingerprint = ?, updated_at = ?, ended_at = ?,
|
|
83
|
+
ending_note = ?
|
|
84
|
+
WHERE id = ?`)
|
|
85
|
+
.run(next.sessionId, next.objective, next.status, next.activatedSeq, next.schedule, next.promptSnapshot, next.needsWrite ? 1 : 0, next.writeApproved ? 1 : 0, next.budgetMessagesCap, next.budgetMessagesUsed, next.budgetFoldedUnits, next.stallStreak, next.wakeStreak, next.lastFingerprint, next.updatedAt, next.endedAt, next.endingNote, id);
|
|
86
|
+
}
|
|
87
|
+
remove(id) {
|
|
88
|
+
this.db.prepare(`DELETE FROM goals WHERE id = ?`).run(id);
|
|
89
|
+
}
|
|
90
|
+
insertWake(goalId, wokeAt, trigger, attribution, fingerprint, progressed) {
|
|
91
|
+
const result = this.db
|
|
92
|
+
.prepare(`INSERT INTO goal_wakes (goal_id, woke_at, trigger, attribution, fingerprint, progressed) VALUES (?, ?, ?, ?, ?, ?)`)
|
|
93
|
+
.run(goalId, wokeAt, trigger, attribution, fingerprint, progressed ? 1 : 0);
|
|
94
|
+
return {
|
|
95
|
+
id: Number(result.lastInsertRowid),
|
|
96
|
+
goalId,
|
|
97
|
+
wokeAt,
|
|
98
|
+
trigger,
|
|
99
|
+
attribution,
|
|
100
|
+
fingerprint,
|
|
101
|
+
progressed,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
wakes(goalId) {
|
|
105
|
+
const rows = this.db
|
|
106
|
+
.prepare(`SELECT id, goal_id, woke_at, trigger, attribution, fingerprint, progressed
|
|
107
|
+
FROM goal_wakes WHERE goal_id = ? ORDER BY id`)
|
|
108
|
+
.all(goalId);
|
|
109
|
+
return rows.map((r) => ({
|
|
110
|
+
id: r.id,
|
|
111
|
+
goalId: r.goal_id,
|
|
112
|
+
wokeAt: r.woke_at,
|
|
113
|
+
trigger: r.trigger,
|
|
114
|
+
attribution: r.attribution,
|
|
115
|
+
fingerprint: r.fingerprint,
|
|
116
|
+
progressed: r.progressed === 1,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** DB 行 → 契约行 */
|
|
121
|
+
function goalFromDb(row) {
|
|
122
|
+
return {
|
|
123
|
+
id: row.id,
|
|
124
|
+
sessionId: row.session_id,
|
|
125
|
+
objective: row.objective,
|
|
126
|
+
status: row.status,
|
|
127
|
+
activatedSeq: row.activated_seq,
|
|
128
|
+
schedule: row.schedule,
|
|
129
|
+
promptSnapshot: row.prompt_snapshot,
|
|
130
|
+
needsWrite: row.needs_write === 1,
|
|
131
|
+
writeApproved: row.write_approved === 1,
|
|
132
|
+
budgetMessagesCap: row.budget_messages_cap,
|
|
133
|
+
budgetMessagesUsed: row.budget_messages_used,
|
|
134
|
+
budgetFoldedUnits: row.budget_folded_units,
|
|
135
|
+
stallStreak: row.stall_streak,
|
|
136
|
+
wakeStreak: row.wake_streak,
|
|
137
|
+
lastFingerprint: row.last_fingerprint,
|
|
138
|
+
createdAt: row.created_at,
|
|
139
|
+
updatedAt: row.updated_at,
|
|
140
|
+
endedAt: row.ended_at,
|
|
141
|
+
endingNote: row.ending_note,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// ── 服务实装 ───────────────────────────────────────────────────────────────
|
|
145
|
+
export function createGoalService(deps) {
|
|
146
|
+
const dao = new GoalDao(deps.db);
|
|
147
|
+
const now = deps.now;
|
|
148
|
+
const warn = deps.warn;
|
|
149
|
+
const newId = deps.newId ?? randomUUID;
|
|
150
|
+
const stallLimit = deps.stallLimit ?? DEFAULT_STALL_LIMIT;
|
|
151
|
+
const wakeBudgetLimit = deps.wakeBudgetLimit ?? DEFAULT_WAKE_BUDGET_LIMIT;
|
|
152
|
+
/** 挂钟窄面(迟到注入前 null——注册需求暂存) */
|
|
153
|
+
let jobsFace = null;
|
|
154
|
+
/** 迟到暂存面(先 goal 后 scheduler 装载序的补接线队列) */
|
|
155
|
+
const pendingClocks = new Set();
|
|
156
|
+
/** 轮间沉淀缓存(goalId → {指纹, 文本}——同指纹零 LLM 单发,批 #99) */
|
|
157
|
+
const depositCache = new Map();
|
|
158
|
+
/** 沉淀单发在飞位(同 goal 至多一次在飞——请求组装路径不排队堆积) */
|
|
159
|
+
const depositInFlight = new Set();
|
|
160
|
+
/** 预算停靠登记(u-3——04 §5 定形注③:parkForBudget 幂等判据 + 件侧唤醒收口现判面;进程内存位——durable 事实由挂钟行 disabled + 会话词承载,daemon 猝死后冷启动经挂钟停摆自然呈现) */
|
|
161
|
+
const parkedForBudget = new Set();
|
|
162
|
+
/** 单漏斗挂钟注册(activate 与 attach 冲洗共用;回执 {ok:false} 上抛响亮) */
|
|
163
|
+
async function registerClock(goal) {
|
|
164
|
+
if (jobsFace === null) {
|
|
165
|
+
pendingClocks.add(goal.id);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const receipt = await jobsFace.register({
|
|
169
|
+
goalId: goal.id,
|
|
170
|
+
sessionId: goal.sessionId,
|
|
171
|
+
schedule: goal.schedule,
|
|
172
|
+
promptSnapshot: goal.promptSnapshot,
|
|
173
|
+
});
|
|
174
|
+
if (!receipt.ok) {
|
|
175
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `挂钟注册失败(goal ${goal.id}):${receipt.message}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 双位合取单源闭包(f-1 定形注③——complete 评测位与 commandGateStatus
|
|
180
|
+
* 读面同源消费此一处,勿另写合取;s 批 U3 收口:评测位手写合取撤)
|
|
181
|
+
*/
|
|
182
|
+
const commandGateStatusOf = (goalId) => {
|
|
183
|
+
const row = dao.get(goalId);
|
|
184
|
+
if (!row || !row.needsWrite)
|
|
185
|
+
return { allowed: false, reason: 'not-declared' };
|
|
186
|
+
if (!row.writeApproved)
|
|
187
|
+
return { allowed: false, reason: 'not-approved' };
|
|
188
|
+
return { allowed: true, reason: 'ok' };
|
|
189
|
+
};
|
|
190
|
+
const service = {
|
|
191
|
+
async activate(req) {
|
|
192
|
+
const bytes = Buffer.byteLength(req.objective, 'utf8');
|
|
193
|
+
if (req.objective.length === 0 || bytes > OBJECTIVE_MAX_BYTES) {
|
|
194
|
+
throw new BaseError('GOAL_GOAL_INVALID', `objective 须非空且 ≤16KiB(得 ${bytes} 字节)`);
|
|
195
|
+
}
|
|
196
|
+
if (dao.activeFor(req.sessionId)) {
|
|
197
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `会话 ${req.sessionId} 已有 active goal(单 active 守卫——先 complete/abandon 再建新)`);
|
|
198
|
+
}
|
|
199
|
+
const ts = now();
|
|
200
|
+
const goal = {
|
|
201
|
+
id: newId(),
|
|
202
|
+
sessionId: req.sessionId,
|
|
203
|
+
objective: req.objective,
|
|
204
|
+
status: 'active',
|
|
205
|
+
activatedSeq: deps.session.length(req.sessionId),
|
|
206
|
+
schedule: req.schedule,
|
|
207
|
+
promptSnapshot: req.promptSnapshot ?? `继续推进目标:${req.objective}`,
|
|
208
|
+
needsWrite: req.needsWrite === true,
|
|
209
|
+
writeApproved: false, // 批准位恒由 /goal approve 后置(f-1 定形注②——申报非授权)
|
|
210
|
+
budgetMessagesCap: req.budgetMessagesCap ?? null,
|
|
211
|
+
budgetMessagesUsed: 0,
|
|
212
|
+
budgetFoldedUnits: 0,
|
|
213
|
+
stallStreak: 0,
|
|
214
|
+
wakeStreak: 0,
|
|
215
|
+
lastFingerprint: null,
|
|
216
|
+
createdAt: ts,
|
|
217
|
+
updatedAt: ts,
|
|
218
|
+
endedAt: null,
|
|
219
|
+
endingNote: null,
|
|
220
|
+
};
|
|
221
|
+
dao.insert(goal);
|
|
222
|
+
try {
|
|
223
|
+
await registerClock(goal);
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
dao.remove(goal.id); // 注册失败回卷建行(不半态——goal 行与挂钟行同笔生死)
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
return dao.get(goal.id);
|
|
230
|
+
},
|
|
231
|
+
async resume(goalId, opts) {
|
|
232
|
+
const row = dao.get(goalId);
|
|
233
|
+
if (!row)
|
|
234
|
+
throw new BaseError('GOAL_NOT_FOUND', `goal「${goalId}」不存在(resume 幽灵 id 零行守卫)`);
|
|
235
|
+
if (row.status !== 'active') {
|
|
236
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `goal「${goalId}」已终态(${row.status})——终态不复活,重开走新 goal`);
|
|
237
|
+
}
|
|
238
|
+
const sessionId = opts?.sessionId ?? row.sessionId;
|
|
239
|
+
// 易主守卫:目标会话已有别的 active goal 时拒绝重绑(单 active 不因易主破例)
|
|
240
|
+
const incumbent = dao.activeFor(sessionId);
|
|
241
|
+
if (incumbent !== undefined && incumbent.id !== goalId) {
|
|
242
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `会话 ${sessionId} 已有 active goal「${incumbent.id}」——重绑撞席拒`);
|
|
243
|
+
}
|
|
244
|
+
dao.update(goalId, {
|
|
245
|
+
sessionId,
|
|
246
|
+
activatedSeq: deps.session.length(sessionId),
|
|
247
|
+
stallStreak: 0,
|
|
248
|
+
wakeStreak: 0,
|
|
249
|
+
lastFingerprint: null,
|
|
250
|
+
}, now());
|
|
251
|
+
await jobsFace?.enable(goalId); // 复活挂钟(停滞硬停后 resume 同律)
|
|
252
|
+
return dao.get(goalId);
|
|
253
|
+
},
|
|
254
|
+
async complete(goalId, evidence) {
|
|
255
|
+
const row = dao.get(goalId);
|
|
256
|
+
if (!row)
|
|
257
|
+
throw new BaseError('GOAL_NOT_FOUND', `goal「${goalId}」不存在(complete 幽灵 id 零行守卫)`);
|
|
258
|
+
if (row.status !== 'active') {
|
|
259
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `goal「${goalId}」已终态(${row.status})——不可再迁转`);
|
|
260
|
+
}
|
|
261
|
+
if (evidence.trim().length === 0) {
|
|
262
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', 'goal_update 终态 completed 必附 evidence(非空——申报与验证分离,自报文本非唯一通道)');
|
|
263
|
+
}
|
|
264
|
+
// 完成否决律:goal-scoped 计划态 open 项机器否决(响亮列 open 项)
|
|
265
|
+
const items = foldGoalTodos(deps.session.events(row.sessionId), row.activatedSeq);
|
|
266
|
+
const open = openGoalItems(items);
|
|
267
|
+
if (open.length > 0) {
|
|
268
|
+
const listing = open.map((item) => `[${item.status}] ${item.content}`).join(';');
|
|
269
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `完成否决——尚有 ${open.length} 个 open 项(open = 一切非 completed 项,deferred 含内无论窗到否):${listing}`);
|
|
270
|
+
}
|
|
271
|
+
// 携 gate 项须全绿(fail-closed 评测——seam 缺席即该门 fail)
|
|
272
|
+
const outcomes = await evaluateGoalGates(items, {
|
|
273
|
+
...deps.gates,
|
|
274
|
+
// 双位合取终判(f-1 定形注③——评测位防御纵深:越路径直写事件流的
|
|
275
|
+
// command gate 条目在此兜底否决;单源闭包消费非手写合取〔s 批 U3〕)
|
|
276
|
+
commandGateAllowed: commandGateStatusOf(goalId).allowed,
|
|
277
|
+
});
|
|
278
|
+
const failed = outcomes.filter((o) => !o.ok);
|
|
279
|
+
if (failed.length > 0) {
|
|
280
|
+
const listing = failed.map((o) => `${o.kind} 门:${o.detail}`).join(';');
|
|
281
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `完成否决——判据门未全绿(${listing})`);
|
|
282
|
+
}
|
|
283
|
+
dao.update(goalId, { status: 'completed', endedAt: now(), endingNote: evidence }, now());
|
|
284
|
+
parkedForBudget.delete(goalId); // 终态清停靠登记(广播面不再辖终态 goal)
|
|
285
|
+
await jobsFace?.disable(goalId); // 终态同笔停摆(行留史)
|
|
286
|
+
return dao.get(goalId);
|
|
287
|
+
},
|
|
288
|
+
async abandon(goalId, reason) {
|
|
289
|
+
const row = dao.get(goalId);
|
|
290
|
+
if (!row)
|
|
291
|
+
throw new BaseError('GOAL_NOT_FOUND', `goal「${goalId}」不存在(abandon 幽灵 id 零行守卫)`);
|
|
292
|
+
if (row.status !== 'active') {
|
|
293
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `goal「${goalId}」已终态(${row.status})——不可再迁转`);
|
|
294
|
+
}
|
|
295
|
+
dao.update(goalId, { status: 'abandoned', endedAt: now(), endingNote: reason ?? 'abandoned' }, now());
|
|
296
|
+
parkedForBudget.delete(goalId); // 终态清停靠登记(广播面不再辖终态 goal)
|
|
297
|
+
await jobsFace?.disable(goalId);
|
|
298
|
+
return dao.get(goalId);
|
|
299
|
+
},
|
|
300
|
+
async wake(goalId, opts) {
|
|
301
|
+
const row = dao.get(goalId);
|
|
302
|
+
if (!row)
|
|
303
|
+
throw new BaseError('GOAL_NOT_FOUND', `goal「${goalId}」不存在(wake 幽灵 id 零行守卫)`);
|
|
304
|
+
// 重绑护栏:goal 关闭(终态)后旧唤醒不落地
|
|
305
|
+
if (row.status !== 'active') {
|
|
306
|
+
return {
|
|
307
|
+
landed: false,
|
|
308
|
+
reason: 'inactive',
|
|
309
|
+
message: `goal「${goalId}」已终态(${row.status})——唤醒不落地(重绑护栏)`,
|
|
310
|
+
goal: row,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const fingerprint = progressFingerprint(foldGoalTodos(deps.session.events(row.sessionId), row.activatedSeq));
|
|
314
|
+
const progressed = row.lastFingerprint === null ? true : fingerprint !== row.lastFingerprint;
|
|
315
|
+
// manual 道:手动起闹——停滞/预算双复位 + 挂钟复活(用户显式意图,双帽不辖)。
|
|
316
|
+
// u-3 停靠让位:人工复位即人工接管——预算停靠登记同笔摘除(广播唤醒面
|
|
317
|
+
// 让位,防广播翻真后对已复活 goal 再 submit 双跑)
|
|
318
|
+
if (opts.trigger === 'manual') {
|
|
319
|
+
parkedForBudget.delete(goalId);
|
|
320
|
+
dao.update(goalId, { stallStreak: 0, wakeStreak: 0, lastFingerprint: fingerprint }, now());
|
|
321
|
+
await jobsFace?.enable(goalId);
|
|
322
|
+
dao.insertWake(goalId, now(), 'manual', opts.attribution, fingerprint, progressed);
|
|
323
|
+
const fresh = dao.get(goalId);
|
|
324
|
+
return { landed: true, reason: 'ok', message: `手动唤醒已落地(停滞计数复位)`, goal: fresh };
|
|
325
|
+
}
|
|
326
|
+
// clock 道:wakeGate 双帽——进展即双复位直落;无进展走双帽判定
|
|
327
|
+
if (progressed) {
|
|
328
|
+
dao.update(goalId, { stallStreak: 0, wakeStreak: 0, lastFingerprint: fingerprint }, now());
|
|
329
|
+
dao.insertWake(goalId, now(), 'clock', opts.attribution, fingerprint, true);
|
|
330
|
+
return { landed: true, reason: 'ok', message: '唤醒落地(计划态有进展)', goal: dao.get(goalId) };
|
|
331
|
+
}
|
|
332
|
+
const stallStreak = row.stallStreak + 1;
|
|
333
|
+
if (stallStreak >= stallLimit) {
|
|
334
|
+
// 停滞硬停:连续 N 轮无进展即停摆报告(不无限空转烧预算)——挂钟停摆
|
|
335
|
+
// 留用户复位道(/goal wake 手动起闹 / resume 重绑),goal 行保持 active
|
|
336
|
+
dao.update(goalId, { stallStreak, lastFingerprint: fingerprint }, now());
|
|
337
|
+
await jobsFace?.disable(goalId);
|
|
338
|
+
warn(`[goal] 停滞硬停:goal「${goalId}」连续 ${stallStreak} 轮唤醒无进展——挂钟停摆(/goal wake 可复位重跑)`);
|
|
339
|
+
return {
|
|
340
|
+
landed: false,
|
|
341
|
+
reason: 'stalled',
|
|
342
|
+
message: `停滞硬停:连续 ${stallStreak} 轮唤醒无进展(帽 ${stallLimit})——挂钟已停摆,/goal wake 手动复位`,
|
|
343
|
+
goal: dao.get(goalId),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const wakeStreak = row.wakeStreak + 1;
|
|
347
|
+
if (wakeStreak >= wakeBudgetLimit) {
|
|
348
|
+
// 唤醒预算帽(§4 maxConsecutiveWakes):连续 clock 唤醒无进展即拒再唤醒
|
|
349
|
+
// (落 warn 不硬停——进展/用户轮/manual 任一即复位)
|
|
350
|
+
dao.update(goalId, { stallStreak, wakeStreak, lastFingerprint: fingerprint }, now());
|
|
351
|
+
warn(`[goal] 唤醒预算拒收:goal「${goalId}」连续 ${wakeStreak} 次无进展 clock 唤醒(帽 ${wakeBudgetLimit})`);
|
|
352
|
+
return {
|
|
353
|
+
landed: false,
|
|
354
|
+
reason: 'wake_budget',
|
|
355
|
+
message: `唤醒预算拒收:连续 ${wakeStreak} 次无进展 clock 唤醒(帽 ${wakeBudgetLimit})——本轮不起`,
|
|
356
|
+
goal: dao.get(goalId),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
dao.update(goalId, { stallStreak, wakeStreak, lastFingerprint: fingerprint }, now());
|
|
360
|
+
dao.insertWake(goalId, now(), 'clock', opts.attribution, fingerprint, false);
|
|
361
|
+
return { landed: true, reason: 'ok', message: '唤醒落地(无进展——停滞/唤醒计数 +1)', goal: dao.get(goalId) };
|
|
362
|
+
},
|
|
363
|
+
get: (goalId) => dao.get(goalId),
|
|
364
|
+
activeFor: (sessionId) => dao.activeFor(sessionId),
|
|
365
|
+
list: () => dao.list(),
|
|
366
|
+
wakes: (goalId) => dao.wakes(goalId),
|
|
367
|
+
async approve(goalId) {
|
|
368
|
+
const row = dao.get(goalId);
|
|
369
|
+
if (!row)
|
|
370
|
+
throw new BaseError('GOAL_NOT_FOUND', `goal「${goalId}」不存在(approve 幽灵 id 零行守卫)`);
|
|
371
|
+
// 批准无对象两档:未申报 needsWrite(approve 前置语义缺位)/ 终态行
|
|
372
|
+
// (生命周期已收口——f-1 定形注②守卫链)
|
|
373
|
+
if (!row.needsWrite) {
|
|
374
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `goal「${goalId}」未申报 needsWrite——无批准对象(activate 时申报后方可批准)`);
|
|
375
|
+
}
|
|
376
|
+
if (row.status !== 'active') {
|
|
377
|
+
throw new BaseError('GOAL_TRANSITION_INVALID', `goal「${goalId}」已终态(${row.status})——批准无对象`);
|
|
378
|
+
}
|
|
379
|
+
if (row.writeApproved)
|
|
380
|
+
return row; // 重复 approve 幂等回执(批准位已置)
|
|
381
|
+
dao.update(goalId, { writeApproved: true }, now());
|
|
382
|
+
return dao.get(goalId);
|
|
383
|
+
},
|
|
384
|
+
commandGateStatus(goalId) {
|
|
385
|
+
// 执行期活查(f-1 定形注③——恒按当前行求值;幽灵行防御位归 not-declared,
|
|
386
|
+
// 正常路径 scope 活则行恒在)——单源闭包消费(s 批 U3:与 complete 评测位同源)
|
|
387
|
+
return commandGateStatusOf(goalId);
|
|
388
|
+
},
|
|
389
|
+
recordTurn(goalId, opts) {
|
|
390
|
+
const row = dao.get(goalId);
|
|
391
|
+
if (!row)
|
|
392
|
+
throw new BaseError('GOAL_NOT_FOUND', `goal「${goalId}」不存在(recordTurn 幽灵 id 零行守卫——装配接线错位)`);
|
|
393
|
+
// 记账单位 = 本窗 durable assistant/message 条数(04 §176——批 #99 驱动
|
|
394
|
+
// 窗扫供给;缺省 1 兼容旧单笔调用形)
|
|
395
|
+
const used = row.budgetMessagesUsed + Math.max(1, Math.floor(opts?.messages ?? 1));
|
|
396
|
+
dao.update(goalId, { budgetMessagesUsed: used, ...(opts?.userInitiated ? { wakeStreak: 0 } : {}) }, now());
|
|
397
|
+
const folded = row.budgetFoldedUnits;
|
|
398
|
+
return {
|
|
399
|
+
braked: row.budgetMessagesCap !== null && used + folded >= row.budgetMessagesCap,
|
|
400
|
+
used,
|
|
401
|
+
cap: row.budgetMessagesCap,
|
|
402
|
+
};
|
|
403
|
+
},
|
|
404
|
+
foldDelegation(goalId, units) {
|
|
405
|
+
const row = dao.get(goalId);
|
|
406
|
+
if (!row)
|
|
407
|
+
throw new BaseError('GOAL_NOT_FOUND', `goal「${goalId}」不存在(foldDelegation 幽灵 id 零行守卫)`);
|
|
408
|
+
dao.update(goalId, { budgetFoldedUnits: row.budgetFoldedUnits + Math.max(0, Math.floor(units)) }, now());
|
|
409
|
+
},
|
|
410
|
+
budgetExceeded(goalId) {
|
|
411
|
+
const row = dao.get(goalId);
|
|
412
|
+
if (!row)
|
|
413
|
+
throw new BaseError('GOAL_NOT_FOUND', `goal「${goalId}」不存在(budgetExceeded 幽灵 id 零行守卫)`);
|
|
414
|
+
return row.budgetMessagesCap !== null && row.budgetMessagesUsed + row.budgetFoldedUnits >= row.budgetMessagesCap;
|
|
415
|
+
},
|
|
416
|
+
async parkForBudget(goalId) {
|
|
417
|
+
const row = dao.get(goalId);
|
|
418
|
+
if (!row)
|
|
419
|
+
throw new BaseError('GOAL_NOT_FOUND', `goal「${goalId}」不存在(parkForBudget 幽灵 id 零行守卫)`);
|
|
420
|
+
// 终态 goal 无停靠语义(重绑护栏同律——终态行挂钟本已停摆)
|
|
421
|
+
if (row.status !== 'active')
|
|
422
|
+
return false;
|
|
423
|
+
// 幂等判据:已停靠 no-op(复验/记账/池检三触发位竞速时首笔落全、余笔蒸发)
|
|
424
|
+
if (parkedForBudget.has(goalId))
|
|
425
|
+
return true;
|
|
426
|
+
parkedForBudget.add(goalId);
|
|
427
|
+
// 挂钟行 disable(起停载体——行留史、广播恢复时 enable 复活;无行 = 静默
|
|
428
|
+
// no-op 既有律)。goal 行 status 三值 CHECK 不动(04 §5 定形注③)
|
|
429
|
+
await jobsFace?.disable(goalId);
|
|
430
|
+
// 会话落停靠词(daemon 猝死后冷启动可恢复呈现——05 §1.1 词行;宿主侧
|
|
431
|
+
// 真身幂等开驱动后 append,会话缺席由窄面真身诚实处置)
|
|
432
|
+
deps.session.appendPaused(row.sessionId);
|
|
433
|
+
warn(`[goal] 预算停靠:goal「${goalId}」(会话 ${row.sessionId})挂钟停摆 + 会话落 session/paused——待 budget_extended 广播唤醒(人工复位道:/goal wake)`);
|
|
434
|
+
return true;
|
|
435
|
+
},
|
|
436
|
+
isParkedForBudget: (goalId) => parkedForBudget.has(goalId),
|
|
437
|
+
unparkForBudget(goalId) {
|
|
438
|
+
parkedForBudget.delete(goalId);
|
|
439
|
+
},
|
|
440
|
+
async reviveClock(goalId) {
|
|
441
|
+
await jobsFace?.enable(goalId);
|
|
442
|
+
},
|
|
443
|
+
depositFor(sessionId) {
|
|
444
|
+
const row = dao.activeFor(sessionId);
|
|
445
|
+
if (row === undefined)
|
|
446
|
+
return null; // 无 active goal = 零注入(驱动同形降级)
|
|
447
|
+
const items = foldGoalTodos(deps.session.events(sessionId), row.activatedSeq);
|
|
448
|
+
const fingerprint = progressFingerprint(items);
|
|
449
|
+
const cached = depositCache.get(row.id);
|
|
450
|
+
if (cached !== undefined && cached.fingerprint === fingerprint)
|
|
451
|
+
return cached.text;
|
|
452
|
+
// 指纹已变(或缓存冷):确定性回退立即承载 + 后台单发刷新(fire-and-
|
|
453
|
+
// forget——请求组装路径零等待;同指纹失败也缓存回退,杜绝每请求重烧)
|
|
454
|
+
const completed = items.filter((item) => item.status === 'completed').length;
|
|
455
|
+
const fallback = `目标:${row.objective}\n计划态:open ${items.length - completed} 项 / completed ${completed} 项`;
|
|
456
|
+
if (deps.summarizer !== undefined && !depositInFlight.has(row.id)) {
|
|
457
|
+
depositInFlight.add(row.id);
|
|
458
|
+
void deps.summarizer
|
|
459
|
+
.complete({ prompt: depositPrompt(row, items), maxChars: GOAL_DEPOSIT_MAX_CHARS })
|
|
460
|
+
.then((out) => {
|
|
461
|
+
depositCache.set(row.id, { fingerprint, text: out.text.trim() === '' ? fallback : out.text });
|
|
462
|
+
})
|
|
463
|
+
.catch((err) => {
|
|
464
|
+
warn(`[goal] 沉淀摘要单发失败(确定性回退承载同指纹缓存):${err instanceof Error ? err.message : String(err)}`);
|
|
465
|
+
depositCache.set(row.id, { fingerprint, text: fallback });
|
|
466
|
+
})
|
|
467
|
+
.finally(() => depositInFlight.delete(row.id));
|
|
468
|
+
}
|
|
469
|
+
return cached !== undefined ? cached.text : fallback;
|
|
470
|
+
},
|
|
471
|
+
goalScopeFor(sessionId) {
|
|
472
|
+
const row = dao.activeFor(sessionId);
|
|
473
|
+
return row === undefined ? undefined : { goalId: row.id, activatedSeq: row.activatedSeq };
|
|
474
|
+
},
|
|
475
|
+
async attachGoalJobsFace(face) {
|
|
476
|
+
jobsFace = face;
|
|
477
|
+
// 迟到冲洗:暂存的挂钟需求逐笔补登记(失败 warn 亮拒不炸装配——goal 行
|
|
478
|
+
// 留在场、挂钟缺席的事实交 /goal list 呈现与用户处置)
|
|
479
|
+
const queued = [...pendingClocks];
|
|
480
|
+
pendingClocks.clear();
|
|
481
|
+
for (const goalId of queued) {
|
|
482
|
+
const row = dao.get(goalId);
|
|
483
|
+
if (!row || row.status !== 'active')
|
|
484
|
+
continue; // 终态/已删行的迟到需求蒸发
|
|
485
|
+
const receipt = await face.register({
|
|
486
|
+
goalId: row.id,
|
|
487
|
+
sessionId: row.sessionId,
|
|
488
|
+
schedule: row.schedule,
|
|
489
|
+
promptSnapshot: row.promptSnapshot,
|
|
490
|
+
});
|
|
491
|
+
if (!receipt.ok) {
|
|
492
|
+
warn(`[goal] 迟到挂钟注册失败:goal「${goalId}」${receipt.message}(挂钟缺席——schedule 坏串请修正后 resume)`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
detachGoalJobsFace() {
|
|
497
|
+
jobsFace = null; // effect 可逆回卷(后续注册再暂存——双序对称)
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
return service;
|
|
501
|
+
}
|