cortico 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/package.json +43 -0
- package/src/boot.ts +46 -0
- package/src/bot.ts +1342 -0
- package/src/config-file.ts +41 -0
- package/src/core/README.md +92 -0
- package/src/core/billing.ts +26 -0
- package/src/core/blobs.ts +106 -0
- package/src/core/bus.ts +311 -0
- package/src/core/config-schema.ts +201 -0
- package/src/core/config.ts +262 -0
- package/src/core/core.ts +699 -0
- package/src/core/cost.ts +244 -0
- package/src/core/event-store.ts +304 -0
- package/src/core/fork.ts +113 -0
- package/src/core/generation.ts +118 -0
- package/src/core/instance-lock.ts +215 -0
- package/src/core/ipc-logger.ts +105 -0
- package/src/core/language.ts +62 -0
- package/src/core/log-context.ts +38 -0
- package/src/core/loop.ts +1858 -0
- package/src/core/markers.ts +48 -0
- package/src/core/prefix.ts +121 -0
- package/src/core/run.ts +95 -0
- package/src/core/secrets.ts +18 -0
- package/src/core/session.ts +56 -0
- package/src/core/sessions.ts +261 -0
- package/src/core/state.ts +104 -0
- package/src/core/template.ts +72 -0
- package/src/core/timers.ts +129 -0
- package/src/core/tool-log.ts +135 -0
- package/src/core/transcript.ts +92 -0
- package/src/core/truncate.ts +121 -0
- package/src/core/types.ts +1140 -0
- package/src/core/usage-log.ts +79 -0
- package/src/core/util.ts +386 -0
- package/src/deploy-listing.ts +160 -0
- package/src/deploy.ts +162 -0
- package/src/extensions/README.md +72 -0
- package/src/extensions/dry-mount.ts +390 -0
- package/src/extensions/manifest.ts +153 -0
- package/src/extensions/runtime.ts +42 -0
- package/src/extensions.ts +519 -0
- package/src/launcher.ts +285 -0
- package/src/paths.ts +155 -0
- package/src/protocol/open-responses/LICENSE +201 -0
- package/src/protocol/open-responses/README.md +14 -0
- package/src/protocol/open-responses/context-helpers.ts +27 -0
- package/src/protocol/open-responses/context-log.ts +59 -0
- package/src/protocol/open-responses/context.ts +89 -0
- package/src/protocol/open-responses/generated.ts +115 -0
- package/src/protocol/open-responses/index.ts +33 -0
- package/src/protocol/open-responses/openapi.json +4230 -0
- package/src/protocol/open-responses/stream.ts +146 -0
- package/src/protocol/open-responses/tokens.ts +31 -0
- package/src/providers/README.md +86 -0
- package/src/providers/base.ts +111 -0
- package/src/providers/configuration.ts +97 -0
- package/src/providers/console/config.ts +21 -0
- package/src/providers/console/settings.ts +443 -0
- package/src/providers/console/strings.ts +74 -0
- package/src/providers/console/types.ts +11 -0
- package/src/providers/llamacpp/archive.ts +148 -0
- package/src/providers/llamacpp/catalog.ts +247 -0
- package/src/providers/llamacpp/config.ts +36 -0
- package/src/providers/llamacpp/console/client.ts +6 -0
- package/src/providers/llamacpp/console/models-panel.ts +120 -0
- package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
- package/src/providers/llamacpp/console/server.ts +153 -0
- package/src/providers/llamacpp/index.ts +95 -0
- package/src/providers/llamacpp/native.ts +51 -0
- package/src/providers/llamacpp/options.ts +117 -0
- package/src/providers/llamacpp/runtime-store.ts +139 -0
- package/src/providers/llamacpp/runtime.ts +214 -0
- package/src/providers/llamacpp/server.ts +286 -0
- package/src/providers/llamacpp/strings.ts +230 -0
- package/src/providers/openai-responses-compat/config.ts +20 -0
- package/src/providers/openai-responses-compat/index.ts +89 -0
- package/src/providers/openai-responses-compat/native.ts +127 -0
- package/src/providers/openai-responses-compat/strings.ts +19 -0
- package/src/providers/pricebook.ts +75 -0
- package/src/providers/registry.ts +137 -0
- package/src/providers/strings.ts +71 -0
- package/src/providers/transport/chat.ts +156 -0
- package/src/providers/transport/errors.ts +48 -0
- package/src/providers/transport/history.ts +68 -0
- package/src/providers/transport/native-input.ts +83 -0
- package/src/providers/transport/native-types.ts +13 -0
- package/src/providers/transport/response-assembly.ts +191 -0
- package/src/providers/transport/response-http.ts +196 -0
- package/src/providers/transport/response-meters.ts +27 -0
- package/src/providers/transport/responses-input.ts +56 -0
- package/src/web/README.md +84 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
- package/src/web/client/console-pages/builtins.ts +10 -0
- package/src/web/client/console-pages/context.ts +135 -0
- package/src/web/client/console-pages/host.ts +633 -0
- package/src/web/client/console-pages/loader.ts +132 -0
- package/src/web/client/console-pages/strings.ts +90 -0
- package/src/web/client/console-pages/tools/strings.ts +49 -0
- package/src/web/client/console-pages/tools/view.ts +215 -0
- package/src/web/client/core/api.ts +200 -0
- package/src/web/client/core/language.ts +56 -0
- package/src/web/client/core/lifecycle.ts +132 -0
- package/src/web/client/core/router.ts +199 -0
- package/src/web/client/core/stream.ts +157 -0
- package/src/web/client/core/websocket.ts +82 -0
- package/src/web/client/features/appearance/index.ts +333 -0
- package/src/web/client/features/appearance/strings.ts +87 -0
- package/src/web/client/features/config/index.ts +11 -0
- package/src/web/client/features/config/strings.ts +41 -0
- package/src/web/client/features/config/view.ts +428 -0
- package/src/web/client/features/core/events.ts +197 -0
- package/src/web/client/features/core/index.ts +254 -0
- package/src/web/client/features/core/run.ts +56 -0
- package/src/web/client/features/core/runlog.ts +214 -0
- package/src/web/client/features/core/sessions.ts +53 -0
- package/src/web/client/features/core/strings.ts +157 -0
- package/src/web/client/features/extensions/index.ts +422 -0
- package/src/web/client/features/extensions/strings.ts +164 -0
- package/src/web/client/features/feature.ts +93 -0
- package/src/web/client/features/live/context.ts +280 -0
- package/src/web/client/features/live/fork.ts +156 -0
- package/src/web/client/features/live/index.ts +392 -0
- package/src/web/client/features/live/onboarding.ts +113 -0
- package/src/web/client/features/live/protocol.ts +122 -0
- package/src/web/client/features/live/sessions.ts +71 -0
- package/src/web/client/features/live/status.ts +68 -0
- package/src/web/client/features/live/strings.ts +252 -0
- package/src/web/client/features/live/timeline.ts +590 -0
- package/src/web/client/features/prompts/editor.ts +292 -0
- package/src/web/client/features/prompts/index.ts +346 -0
- package/src/web/client/features/prompts/strings.ts +136 -0
- package/src/web/client/features/prompts/view.ts +256 -0
- package/src/web/client/features/providers/index.ts +100 -0
- package/src/web/client/features/providers/strings.ts +23 -0
- package/src/web/client/features/settings/general.ts +28 -0
- package/src/web/client/features/settings/index.ts +82 -0
- package/src/web/client/features/settings/strings.ts +33 -0
- package/src/web/client/features/storage/index.ts +1 -0
- package/src/web/client/features/storage/strings.ts +45 -0
- package/src/web/client/features/storage/view.ts +154 -0
- package/src/web/client/features/usage/chart.ts +486 -0
- package/src/web/client/features/usage/index.ts +407 -0
- package/src/web/client/features/usage/labels.ts +46 -0
- package/src/web/client/features/usage/range.ts +36 -0
- package/src/web/client/features/usage/state.ts +37 -0
- package/src/web/client/features/usage/strings.ts +191 -0
- package/src/web/client/features/usage/tooltip.ts +86 -0
- package/src/web/client/features/usage/types.ts +96 -0
- package/src/web/client/features/worlds/index.ts +424 -0
- package/src/web/client/features/worlds/strings.ts +121 -0
- package/src/web/client/main.ts +273 -0
- package/src/web/client/shell/avatar.ts +233 -0
- package/src/web/client/shell/index.ts +549 -0
- package/src/web/client/shell/strings.ts +89 -0
- package/src/web/client/theme/handoff.ts +43 -0
- package/src/web/client/theme/palette.ts +109 -0
- package/src/web/client/theme/registry.ts +130 -0
- package/src/web/client/theme/storage.ts +72 -0
- package/src/web/client/theme/strings.ts +123 -0
- package/src/web/client/theme/studio.ts +366 -0
- package/src/web/client/ui/actions.ts +109 -0
- package/src/web/client/ui/data.ts +185 -0
- package/src/web/client/ui/dom.ts +23 -0
- package/src/web/client/ui/fields.ts +191 -0
- package/src/web/client/ui/format.ts +75 -0
- package/src/web/client/ui/icons.ts +183 -0
- package/src/web/client/ui/images.ts +91 -0
- package/src/web/client/ui/index.ts +89 -0
- package/src/web/client/ui/lamp.ts +125 -0
- package/src/web/client/ui/log.ts +98 -0
- package/src/web/client/ui/overlay.ts +184 -0
- package/src/web/client/ui/page.ts +8 -0
- package/src/web/client/ui/prompt-input.tsx +311 -0
- package/src/web/client/ui/sheet.ts +131 -0
- package/src/web/client/ui/strings.ts +71 -0
- package/src/web/console-pages.ts +367 -0
- package/src/web/files.ts +107 -0
- package/src/web/path-picker.ts +296 -0
- package/src/web/public/index.html +17 -0
- package/src/web/public/styles.css +1715 -0
- package/src/web/server.ts +1883 -0
- package/src/web/shared/client-panel.ts +603 -0
- package/src/web/shared/console-protocol.ts +560 -0
- package/src/web/shared/css.d.ts +1 -0
- package/src/web/shared/path-picker.ts +23 -0
- package/src/web/shared/theme.ts +256 -0
- package/src/web/theme-store.ts +37 -0
- package/src/world.ts +449 -0
- package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
- package/src/worlds/bilibili/README.md +176 -0
- package/src/worlds/bilibili/audience-admission.ts +995 -0
- package/src/worlds/bilibili/client.ts +528 -0
- package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
- package/src/worlds/bilibili/config.ts +185 -0
- package/src/worlds/bilibili/console/client.ts +202 -0
- package/src/worlds/bilibili/definition.ts +35 -0
- package/src/worlds/bilibili/gift-frame.ts +131 -0
- package/src/worlds/bilibili/normalize.ts +452 -0
- package/src/worlds/bilibili/overlay/announcement.ts +76 -0
- package/src/worlds/bilibili/overlay/assets.ts +82 -0
- package/src/worlds/bilibili/overlay/model.ts +494 -0
- package/src/worlds/bilibili/overlay/project.ts +202 -0
- package/src/worlds/bilibili/overlay/server.ts +286 -0
- package/src/worlds/bilibili/overlay/types.ts +217 -0
- package/src/worlds/bilibili/overlay/web/app.js +690 -0
- package/src/worlds/bilibili/overlay/web/editor.css +297 -0
- package/src/worlds/bilibili/overlay/web/editor.html +102 -0
- package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
- package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
- package/src/worlds/bilibili/overlay/web/styles.css +258 -0
- package/src/worlds/bilibili/protobuf.ts +123 -0
- package/src/worlds/bilibili/wire.ts +94 -0
- package/src/worlds/bilibili/world.ts +1389 -0
- package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
- package/src/worlds/console-fixture/console/client.ts +72 -0
- package/src/worlds/console-fixture/world.ts +73 -0
- package/src/worlds/index.ts +15 -0
- package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
- package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
- package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
- package/src/worlds/minecraft/README.md +917 -0
- package/src/worlds/minecraft/blueprint-plan.ts +915 -0
- package/src/worlds/minecraft/blueprint-registry.ts +566 -0
- package/src/worlds/minecraft/blueprint-repair.ts +236 -0
- package/src/worlds/minecraft/blueprint-resource.ts +272 -0
- package/src/worlds/minecraft/blueprint.ts +492 -0
- package/src/worlds/minecraft/body-lease.ts +340 -0
- package/src/worlds/minecraft/bridge.ts +749 -0
- package/src/worlds/minecraft/check.ts +651 -0
- package/src/worlds/minecraft/chests.ts +292 -0
- package/src/worlds/minecraft/client-launch.ts +272 -0
- package/src/worlds/minecraft/client-options.ts +85 -0
- package/src/worlds/minecraft/client-skins.ts +164 -0
- package/src/worlds/minecraft/client-window.ps1 +91 -0
- package/src/worlds/minecraft/client.ts +423 -0
- package/src/worlds/minecraft/combat-context.ts +109 -0
- package/src/worlds/minecraft/combat.ts +1552 -0
- package/src/worlds/minecraft/config.ts +499 -0
- package/src/worlds/minecraft/console/access.ts +218 -0
- package/src/worlds/minecraft/console/client.ts +244 -0
- package/src/worlds/minecraft/console/log.ts +110 -0
- package/src/worlds/minecraft/console/mount.ts +420 -0
- package/src/worlds/minecraft/console/skin.ts +257 -0
- package/src/worlds/minecraft/console/style.css +154 -0
- package/src/worlds/minecraft/console/world.ts +449 -0
- package/src/worlds/minecraft/deaths.ts +135 -0
- package/src/worlds/minecraft/definition.ts +18 -0
- package/src/worlds/minecraft/engine-child.ts +274 -0
- package/src/worlds/minecraft/engine-ipc.ts +98 -0
- package/src/worlds/minecraft/entity-facts.ts +132 -0
- package/src/worlds/minecraft/escape.ts +407 -0
- package/src/worlds/minecraft/executor.ts +14494 -0
- package/src/worlds/minecraft/explored.ts +156 -0
- package/src/worlds/minecraft/geometry.ts +205 -0
- package/src/worlds/minecraft/goal-plan.ts +647 -0
- package/src/worlds/minecraft/item-break.ts +126 -0
- package/src/worlds/minecraft/item-facts.ts +146 -0
- package/src/worlds/minecraft/item-pick.ts +67 -0
- package/src/worlds/minecraft/level-dat.ts +176 -0
- package/src/worlds/minecraft/log.ts +132 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
- package/src/worlds/minecraft/names.ts +377 -0
- package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
- package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
- package/src/worlds/minecraft/piglin.ts +49 -0
- package/src/worlds/minecraft/policy.ts +547 -0
- package/src/worlds/minecraft/precheck.ts +655 -0
- package/src/worlds/minecraft/proxy.ts +445 -0
- package/src/worlds/minecraft/ranged.ts +635 -0
- package/src/worlds/minecraft/readouts.ts +102 -0
- package/src/worlds/minecraft/round.ts +26 -0
- package/src/worlds/minecraft/search-observation.ts +92 -0
- package/src/worlds/minecraft/server-config.ts +467 -0
- package/src/worlds/minecraft/server.ts +658 -0
- package/src/worlds/minecraft/show.ts +84 -0
- package/src/worlds/minecraft/skills.ts +1763 -0
- package/src/worlds/minecraft/terrain.ts +1735 -0
- package/src/worlds/minecraft/window.ts +96 -0
- package/src/worlds/minecraft/works.ts +179 -0
- package/src/worlds/minecraft/world.ts +6438 -0
- package/src/worlds/qq/ENV_PROMPT.md +5 -0
- package/src/worlds/qq/config.ts +108 -0
- package/src/worlds/qq/console/client.ts +126 -0
- package/src/worlds/qq/console/events.ts +155 -0
- package/src/worlds/qq/console/gate.ts +232 -0
- package/src/worlds/qq/console/roster.ts +203 -0
- package/src/worlds/qq/console/style.css +65 -0
- package/src/worlds/qq/conversation.ts +28 -0
- package/src/worlds/qq/definition.ts +62 -0
- package/src/worlds/qq/driver.ts +375 -0
- package/src/worlds/qq/history-tools.ts +209 -0
- package/src/worlds/qq/normalize.ts +323 -0
- package/src/worlds/qq/qface-map.ts +282 -0
- package/src/worlds/qq/vision-prompt.ts +27 -0
- package/src/worlds/qq/vision.ts +642 -0
- package/src/worlds/qq/vlm.ts +142 -0
- package/src/worlds/qq/world.ts +1503 -0
- package/src/worlds/terminal/ENV_PROMPT.md +11 -0
- package/src/worlds/terminal/config.ts +9 -0
- package/src/worlds/terminal/definition.ts +14 -0
- package/src/worlds/terminal/world.ts +700 -0
- package/src/worlds/websearch/ENV_PROMPT.md +1 -0
- package/src/worlds/websearch/brave-client.ts +261 -0
- package/src/worlds/websearch/config.ts +75 -0
- package/src/worlds/websearch/definition.ts +20 -0
- package/src/worlds/websearch/world.ts +171 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
import { diffBlueprint } from './blueprint-plan.ts';
|
|
2
|
+
import { CHECK_BOX_CELL_CAP, evalAssert, type CheckResult, type CheckWorld, type CheckVerdict } from './check.ts';
|
|
3
|
+
import type { PositionXYZ } from './blueprint.ts';
|
|
4
|
+
|
|
5
|
+
const GOAL_PLAN_MAX_STEPS = 12;
|
|
6
|
+
const GOAL_PLAN_MAX_PROBES_PER_STEP = 8;
|
|
7
|
+
const GOAL_PLAN_MAX_PROBES = 24;
|
|
8
|
+
|
|
9
|
+
type GoalCompare = { op: '=' | '>=' | '<='; n: number };
|
|
10
|
+
type Region = { min: PositionXYZ; max: PositionXYZ; dimension: string };
|
|
11
|
+
|
|
12
|
+
type GoalProbe =
|
|
13
|
+
| { source: 'block'; at: PositionXYZ; is: string }
|
|
14
|
+
| { source: 'inventory'; item: string; count: GoalCompare }
|
|
15
|
+
| { source: 'blueprint'; key: string; loaded: boolean | null; matched: GoalCompare | null }
|
|
16
|
+
| { source: 'position'; region: Region }
|
|
17
|
+
| { source: 'entity'; type: string; region: Region; count: GoalCompare }
|
|
18
|
+
| { source: 'item'; item: string; region: Region; count: GoalCompare }
|
|
19
|
+
| {
|
|
20
|
+
source: 'status';
|
|
21
|
+
field: 'health' | 'food' | 'oxygen' | 'burning';
|
|
22
|
+
equals: boolean | GoalCompare;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
interface GoalEvidence {
|
|
26
|
+
observedAt: number;
|
|
27
|
+
connectionGeneration: number;
|
|
28
|
+
realm: string;
|
|
29
|
+
dimension: string;
|
|
30
|
+
verdict: CheckVerdict;
|
|
31
|
+
results: CheckResult[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface GoalJudgmentEvidence {
|
|
35
|
+
judgedAt: number;
|
|
36
|
+
note: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface GoalPlanStep {
|
|
40
|
+
do: string;
|
|
41
|
+
verify: GoalProbe[] | null;
|
|
42
|
+
judgment: string | null;
|
|
43
|
+
state: 'pending' | 'verified' | 'judged';
|
|
44
|
+
evidence: GoalEvidence | GoalJudgmentEvidence | null;
|
|
45
|
+
completedAt: number | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface GoalPlan {
|
|
49
|
+
steps: GoalPlanStep[];
|
|
50
|
+
readyAnnounced: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface GoalEntityReading {
|
|
54
|
+
type: string;
|
|
55
|
+
position: PositionXYZ;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface GoalItemReading {
|
|
59
|
+
item: string;
|
|
60
|
+
count: number;
|
|
61
|
+
position: PositionXYZ;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Current read facade. Every function is evaluated from the live Mineflayer state. */
|
|
65
|
+
export interface GoalProbeContext {
|
|
66
|
+
observedAt: number;
|
|
67
|
+
connectionGeneration: number;
|
|
68
|
+
realm: string;
|
|
69
|
+
dimension: string;
|
|
70
|
+
world: CheckWorld;
|
|
71
|
+
position(): readonly [number, number, number];
|
|
72
|
+
status(): Readonly<{
|
|
73
|
+
health: number;
|
|
74
|
+
food: number;
|
|
75
|
+
oxygen: number;
|
|
76
|
+
burning: boolean;
|
|
77
|
+
}>;
|
|
78
|
+
entities(): readonly GoalEntityReading[];
|
|
79
|
+
items(): readonly GoalItemReading[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type GoalPlanTransition =
|
|
83
|
+
| { kind: 'advance'; from: number; to: number; total: number; next: string | null; evidence: string[] }
|
|
84
|
+
| { kind: 'regress'; from: number; to: number; total: number; step: number; evidence: string[] }
|
|
85
|
+
| { kind: 'ready'; total: number };
|
|
86
|
+
|
|
87
|
+
interface GoalPlanCoordination {
|
|
88
|
+
transitions: GoalPlanTransition[];
|
|
89
|
+
completed: number;
|
|
90
|
+
total: number;
|
|
91
|
+
next: GoalPlanStep | null;
|
|
92
|
+
ready: boolean;
|
|
93
|
+
blocker: GoalEvidence | null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const VEC3_SCHEMA = {
|
|
97
|
+
type: 'array',
|
|
98
|
+
minItems: 3,
|
|
99
|
+
maxItems: 3,
|
|
100
|
+
items: { type: 'integer' },
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const REGION_SCHEMA = {
|
|
104
|
+
type: 'array',
|
|
105
|
+
minItems: 2,
|
|
106
|
+
maxItems: 2,
|
|
107
|
+
items: VEC3_SCHEMA,
|
|
108
|
+
description: '两个角 [[x1,y1,z1],[x2,y2,z2]];必须同时给 dimension。',
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const COMPARE_SCHEMA = {
|
|
112
|
+
type: ['number', 'string'],
|
|
113
|
+
description: '非负数表示恰好;也可写 ">=8" / "<=2"。',
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Flat fields stay visible to tool decoders that discard fields nested under oneOf. */
|
|
117
|
+
const GOAL_PROBE_SCHEMA = {
|
|
118
|
+
type: 'object',
|
|
119
|
+
description: '机械旁证。source 决定字段:block(at,is);inventory(item,count);blueprint(key,loaded?/matched?);position(region,dimension);entity(type,region,dimension,count);item(item,region,dimension,count);status(field,equals)。',
|
|
120
|
+
properties: {
|
|
121
|
+
source: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
enum: ['block', 'inventory', 'blueprint', 'position', 'entity', 'item', 'status'],
|
|
124
|
+
},
|
|
125
|
+
at: VEC3_SCHEMA,
|
|
126
|
+
is: { type: 'string', description: 'block 的精确方块 id。' },
|
|
127
|
+
item: { type: 'string', description: 'inventory/item 的精确物品 id。' },
|
|
128
|
+
type: { type: 'string', description: 'entity 的精确实体 id。' },
|
|
129
|
+
count: COMPARE_SCHEMA,
|
|
130
|
+
key: { type: 'string', description: 'blueprint 的蓝图键。' },
|
|
131
|
+
loaded: { type: 'boolean', description: 'blueprint 是否已装载。' },
|
|
132
|
+
matched: COMPARE_SCHEMA,
|
|
133
|
+
region: REGION_SCHEMA,
|
|
134
|
+
dimension: { type: 'string', description: 'region 所在维度,如 minecraft:overworld。' },
|
|
135
|
+
field: { type: 'string', enum: ['health', 'food', 'oxygen', 'burning'] },
|
|
136
|
+
equals: {
|
|
137
|
+
type: ['number', 'string', 'boolean'],
|
|
138
|
+
description: 'status 的目标值;数值状态也可写 ">=8" / "<=2"。',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
required: ['source'],
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const GOAL_PLAN_SCHEMA = {
|
|
145
|
+
type: 'array',
|
|
146
|
+
minItems: 1,
|
|
147
|
+
maxItems: GOAL_PLAN_MAX_STEPS,
|
|
148
|
+
description: '按执行顺序列里程碑。每步恰好给 verify 或 judgment;verify 数组按 AND 求值。',
|
|
149
|
+
items: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
do: { type: 'string', description: '这一步要实施什么。' },
|
|
153
|
+
verify: {
|
|
154
|
+
type: ['object', 'array'],
|
|
155
|
+
description: '一个机械旁证,或最多 8 个旁证组成的 AND 数组。',
|
|
156
|
+
properties: GOAL_PROBE_SCHEMA.properties,
|
|
157
|
+
required: ['source'],
|
|
158
|
+
items: GOAL_PROBE_SCHEMA,
|
|
159
|
+
minItems: 1,
|
|
160
|
+
maxItems: GOAL_PLAN_MAX_PROBES_PER_STEP,
|
|
161
|
+
},
|
|
162
|
+
judgment: {
|
|
163
|
+
type: 'string',
|
|
164
|
+
description: '说明为什么这一步只能由你现场判断;完成时再用 milestone 提交判断说明。',
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
required: ['do'],
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
function objectOf(value: unknown): Record<string, unknown> | null {
|
|
172
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
173
|
+
? value as Record<string, unknown>
|
|
174
|
+
: null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function unknownFields(body: Record<string, unknown>, allowed: readonly string[]): string[] {
|
|
178
|
+
const keep = new Set(allowed);
|
|
179
|
+
return Object.keys(body).filter((key) => !keep.has(key));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function text(value: unknown): string | null {
|
|
183
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function exactId(value: unknown): string | null {
|
|
187
|
+
const raw = text(value);
|
|
188
|
+
return raw?.toLowerCase().replace(/^minecraft:/, '') ?? null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function parsePos(value: unknown, path: string): PositionXYZ | { error: string } {
|
|
192
|
+
if (!Array.isArray(value) || value.length !== 3) return { error: `${path} 要 [x,y,z] 三个整数` };
|
|
193
|
+
if (value.some((part) => typeof part !== 'number' || !Number.isFinite(part))) {
|
|
194
|
+
return { error: `${path} 要 [x,y,z] 三个整数` };
|
|
195
|
+
}
|
|
196
|
+
return value.map((part) => Math.floor(part as number)) as PositionXYZ;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function parseCompare(value: unknown, path: string): GoalCompare | { error: string } {
|
|
200
|
+
if (typeof value === 'number' && Number.isFinite(value) && value >= 0) return { op: '=', n: value };
|
|
201
|
+
if (typeof value === 'string') {
|
|
202
|
+
const found = /^\s*(>=|<=|=)?\s*(\d+(?:\.\d+)?)\s*$/.exec(value);
|
|
203
|
+
if (found) return { op: (found[1] ?? '=') as GoalCompare['op'], n: Number(found[2]) };
|
|
204
|
+
}
|
|
205
|
+
return { error: `${path} 要非负数或 ">=8" / "<=2"` };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function parseRegion(body: Record<string, unknown>, path: string): Region | { error: string } {
|
|
209
|
+
if (!Array.isArray(body.region) || body.region.length !== 2) {
|
|
210
|
+
return { error: `${path}.region 要 [[x1,y1,z1],[x2,y2,z2]]` };
|
|
211
|
+
}
|
|
212
|
+
const a = parsePos(body.region[0], `${path}.region[0]`);
|
|
213
|
+
if ('error' in a) return a;
|
|
214
|
+
const b = parsePos(body.region[1], `${path}.region[1]`);
|
|
215
|
+
if ('error' in b) return b;
|
|
216
|
+
const dimension = text(body.dimension);
|
|
217
|
+
if (!dimension) return { error: `${path}.dimension 要明确写维度` };
|
|
218
|
+
const min: PositionXYZ = [Math.min(a[0], b[0]), Math.min(a[1], b[1]), Math.min(a[2], b[2])];
|
|
219
|
+
const max: PositionXYZ = [Math.max(a[0], b[0]), Math.max(a[1], b[1]), Math.max(a[2], b[2])];
|
|
220
|
+
const dx = max[0] - min[0] + 1;
|
|
221
|
+
const dy = max[1] - min[1] + 1;
|
|
222
|
+
const dz = max[2] - min[2] + 1;
|
|
223
|
+
const cells = dx * dy * dz;
|
|
224
|
+
if (cells > CHECK_BOX_CELL_CAP) {
|
|
225
|
+
// 将体积上限换算成边长,并报告本次三条边长,供调用方缩小范围。
|
|
226
|
+
const edge = Math.floor(Math.cbrt(CHECK_BOX_CELL_CAP));
|
|
227
|
+
return {
|
|
228
|
+
error: `${path}.region 共 ${cells} 格,超过一次能核的 ${CHECK_BOX_CELL_CAP} 格`
|
|
229
|
+
+ `(${CHECK_BOX_CELL_CAP} 格 ≈ ${edge}×${edge}×${edge};你这次是 ${dx}×${dy}×${dz})`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return { min, max, dimension };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function parseProbe(value: unknown, path: string): GoalProbe | { error: string } {
|
|
236
|
+
const body = objectOf(value);
|
|
237
|
+
if (!body) return { error: `${path} 要一个旁证对象` };
|
|
238
|
+
const source = body.source;
|
|
239
|
+
if (typeof source !== 'string') return { error: `${path}.source 要一个来源名` };
|
|
240
|
+
const allowedBySource: Record<string, readonly string[]> = {
|
|
241
|
+
block: ['source', 'at', 'is'],
|
|
242
|
+
inventory: ['source', 'item', 'count'],
|
|
243
|
+
blueprint: ['source', 'key', 'loaded', 'matched'],
|
|
244
|
+
position: ['source', 'region', 'dimension'],
|
|
245
|
+
entity: ['source', 'type', 'region', 'dimension', 'count'],
|
|
246
|
+
item: ['source', 'item', 'region', 'dimension', 'count'],
|
|
247
|
+
status: ['source', 'field', 'equals'],
|
|
248
|
+
};
|
|
249
|
+
const allowed = allowedBySource[source];
|
|
250
|
+
if (!allowed) return { error: `${path}.source 不认识「${source}」` };
|
|
251
|
+
const extras = unknownFields(body, allowed);
|
|
252
|
+
if (extras.length > 0) return { error: `${path} 有不属于 ${source} 的字段: ${extras.join(', ')}` };
|
|
253
|
+
|
|
254
|
+
if (source === 'block') {
|
|
255
|
+
const at = parsePos(body.at, `${path}.at`);
|
|
256
|
+
if ('error' in at) return at;
|
|
257
|
+
const is = exactId(body.is);
|
|
258
|
+
return is ? { source, at, is } : { error: `${path}.is 要精确方块 id` };
|
|
259
|
+
}
|
|
260
|
+
if (source === 'inventory') {
|
|
261
|
+
const item = exactId(body.item);
|
|
262
|
+
if (!item) return { error: `${path}.item 要精确物品 id` };
|
|
263
|
+
const count = parseCompare(body.count, `${path}.count`);
|
|
264
|
+
return 'error' in count ? count : { source, item, count };
|
|
265
|
+
}
|
|
266
|
+
if (source === 'blueprint') {
|
|
267
|
+
const key = text(body.key);
|
|
268
|
+
if (!key) return { error: `${path}.key 要蓝图键` };
|
|
269
|
+
if (body.loaded !== undefined && typeof body.loaded !== 'boolean') {
|
|
270
|
+
return { error: `${path}.loaded 只收 true/false` };
|
|
271
|
+
}
|
|
272
|
+
const matched = body.matched === undefined ? null : parseCompare(body.matched, `${path}.matched`);
|
|
273
|
+
if (matched && 'error' in matched) return matched;
|
|
274
|
+
const loaded = typeof body.loaded === 'boolean' ? body.loaded : null;
|
|
275
|
+
if (loaded === null && matched === null) return { error: `${path} 至少给 loaded 或 matched` };
|
|
276
|
+
if (loaded === false && matched !== null) return { error: `${path} 不能同时要求 loaded=false 和 matched` };
|
|
277
|
+
return { source, key, loaded, matched };
|
|
278
|
+
}
|
|
279
|
+
if (source === 'position') {
|
|
280
|
+
const region = parseRegion(body, path);
|
|
281
|
+
return 'error' in region ? region : { source, region };
|
|
282
|
+
}
|
|
283
|
+
if (source === 'entity') {
|
|
284
|
+
const type = exactId(body.type);
|
|
285
|
+
if (!type) return { error: `${path}.type 要精确实体 id` };
|
|
286
|
+
const region = parseRegion(body, path);
|
|
287
|
+
if ('error' in region) return region;
|
|
288
|
+
const count = parseCompare(body.count, `${path}.count`);
|
|
289
|
+
return 'error' in count ? count : { source, type, region, count };
|
|
290
|
+
}
|
|
291
|
+
if (source === 'item') {
|
|
292
|
+
const item = exactId(body.item);
|
|
293
|
+
if (!item) return { error: `${path}.item 要精确物品 id` };
|
|
294
|
+
const region = parseRegion(body, path);
|
|
295
|
+
if ('error' in region) return region;
|
|
296
|
+
const count = parseCompare(body.count, `${path}.count`);
|
|
297
|
+
return 'error' in count ? count : { source, item, region, count };
|
|
298
|
+
}
|
|
299
|
+
const field = body.field;
|
|
300
|
+
if (!['health', 'food', 'oxygen', 'burning'].includes(String(field))) {
|
|
301
|
+
return { error: `${path}.field 只收 health/food/oxygen/burning` };
|
|
302
|
+
}
|
|
303
|
+
if (field === 'burning') {
|
|
304
|
+
return typeof body.equals === 'boolean'
|
|
305
|
+
? { source: 'status', field, equals: body.equals }
|
|
306
|
+
: { error: `${path}.equals 对 burning 只收 true/false` };
|
|
307
|
+
}
|
|
308
|
+
const equals = parseCompare(body.equals, `${path}.equals`);
|
|
309
|
+
return 'error' in equals
|
|
310
|
+
? equals
|
|
311
|
+
: { source: 'status', field: field as 'health' | 'food' | 'oxygen', equals };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function parseGoalPlan(value: unknown): GoalPlan | { error: string } {
|
|
315
|
+
if (!Array.isArray(value) || value.length === 0) return { error: 'add.plan 至少要一步' };
|
|
316
|
+
if (value.length > GOAL_PLAN_MAX_STEPS) {
|
|
317
|
+
return { error: `add.plan 一次最多 ${GOAL_PLAN_MAX_STEPS} 步` };
|
|
318
|
+
}
|
|
319
|
+
const steps: GoalPlanStep[] = [];
|
|
320
|
+
let probeCount = 0;
|
|
321
|
+
for (const [index, raw] of value.entries()) {
|
|
322
|
+
const path = `add.plan[${index}]`;
|
|
323
|
+
const body = objectOf(raw);
|
|
324
|
+
if (!body) return { error: `${path} 要一个对象` };
|
|
325
|
+
const extras = unknownFields(body, ['do', 'verify', 'judgment']);
|
|
326
|
+
if (extras.length > 0) return { error: `${path} 有未知字段: ${extras.join(', ')}` };
|
|
327
|
+
const action = text(body.do);
|
|
328
|
+
if (!action) return { error: `${path}.do 不能空` };
|
|
329
|
+
const hasVerify = body.verify !== undefined;
|
|
330
|
+
const hasJudgment = body.judgment !== undefined;
|
|
331
|
+
if (hasVerify === hasJudgment) return { error: `${path} 要恰好给 verify 或 judgment` };
|
|
332
|
+
if (hasJudgment) {
|
|
333
|
+
const judgment = text(body.judgment);
|
|
334
|
+
if (!judgment) return { error: `${path}.judgment 不能空` };
|
|
335
|
+
steps.push({ do: action, verify: null, judgment, state: 'pending', evidence: null, completedAt: null });
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const rawProbes = Array.isArray(body.verify) ? body.verify : [body.verify];
|
|
339
|
+
if (rawProbes.length === 0 || rawProbes.length > GOAL_PLAN_MAX_PROBES_PER_STEP) {
|
|
340
|
+
return { error: `${path}.verify 要 1..${GOAL_PLAN_MAX_PROBES_PER_STEP} 个旁证` };
|
|
341
|
+
}
|
|
342
|
+
probeCount += rawProbes.length;
|
|
343
|
+
if (probeCount > GOAL_PLAN_MAX_PROBES) {
|
|
344
|
+
return { error: `add.plan 一共最多 ${GOAL_PLAN_MAX_PROBES} 个旁证` };
|
|
345
|
+
}
|
|
346
|
+
const probes: GoalProbe[] = [];
|
|
347
|
+
for (const [probeIndex, rawProbe] of rawProbes.entries()) {
|
|
348
|
+
const parsed = parseProbe(rawProbe, `${path}.verify[${probeIndex}]`);
|
|
349
|
+
if ('error' in parsed) return parsed;
|
|
350
|
+
probes.push(parsed);
|
|
351
|
+
}
|
|
352
|
+
steps.push({ do: action, verify: probes, judgment: null, state: 'pending', evidence: null, completedAt: null });
|
|
353
|
+
}
|
|
354
|
+
return { steps, readyAnnounced: false };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function compare(actual: number, wanted: GoalCompare): boolean {
|
|
358
|
+
return wanted.op === '=' ? actual === wanted.n : wanted.op === '>=' ? actual >= wanted.n : actual <= wanted.n;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function compareText(wanted: GoalCompare): string {
|
|
362
|
+
return `${wanted.op}${wanted.n}`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function intervalVerdict(min: number, max: number, wanted: GoalCompare): CheckVerdict {
|
|
366
|
+
if (wanted.op === '=') {
|
|
367
|
+
if (wanted.n < min || wanted.n > max) return 'bad';
|
|
368
|
+
return min === max && min === wanted.n ? 'ok' : 'unknown';
|
|
369
|
+
}
|
|
370
|
+
if (wanted.op === '>=') return min >= wanted.n ? 'ok' : max < wanted.n ? 'bad' : 'unknown';
|
|
371
|
+
return max <= wanted.n ? 'ok' : min > wanted.n ? 'bad' : 'unknown';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function inRegion(position: readonly [number, number, number], region: Region): boolean {
|
|
375
|
+
return position.every((part, axis) => part >= region.min[axis] && part <= region.max[axis]);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function regionObservable(region: Region, world: CheckWorld): boolean {
|
|
379
|
+
const y = Math.floor((region.min[1] + region.max[1]) / 2);
|
|
380
|
+
const xs = new Set<number>([region.min[0], region.max[0]]);
|
|
381
|
+
const zs = new Set<number>([region.min[2], region.max[2]]);
|
|
382
|
+
for (let x = region.min[0]; x <= region.max[0]; x += 16) xs.add(x);
|
|
383
|
+
for (let z = region.min[2]; z <= region.max[2]; z += 16) zs.add(z);
|
|
384
|
+
for (const x of xs) for (const z of zs) if (world.cell(x, y, z) === null) return false;
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function evidenceResult(verdict: CheckVerdict, text: string): CheckResult {
|
|
389
|
+
return { verdict, text };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function evaluateProbe(probe: GoalProbe, ctx: GoalProbeContext): CheckResult {
|
|
393
|
+
if (probe.source === 'block') return evalAssert({ kind: 'at', at: probe.at, is: probe.is }, ctx.world);
|
|
394
|
+
if (probe.source === 'inventory') {
|
|
395
|
+
return evalAssert({ kind: 'inv', want: [{ item: probe.item, ...probe.count }] }, ctx.world);
|
|
396
|
+
}
|
|
397
|
+
if (probe.source === 'blueprint') {
|
|
398
|
+
const site = ctx.world.site(probe.key);
|
|
399
|
+
if (probe.loaded !== null && Boolean(site) !== probe.loaded) {
|
|
400
|
+
return evidenceResult('bad', `蓝图「${probe.key}」loaded=${Boolean(site)},要 ${probe.loaded}`);
|
|
401
|
+
}
|
|
402
|
+
if (!probe.matched) return evidenceResult('ok', `蓝图「${probe.key}」loaded=${Boolean(site)}`);
|
|
403
|
+
if (!site) return evidenceResult('error', `蓝图「${probe.key}」没装载,读不到 matched`);
|
|
404
|
+
if (!site.anchor) return evidenceResult('error', `蓝图「${probe.key}」还没绑工地锚点`);
|
|
405
|
+
const diff = diffBlueprint(
|
|
406
|
+
site.blueprint,
|
|
407
|
+
site.plan,
|
|
408
|
+
site.anchor,
|
|
409
|
+
(x, y, z) => ctx.world.cell(x, y, z)?.state ?? null,
|
|
410
|
+
{ checkAir: true },
|
|
411
|
+
);
|
|
412
|
+
const verdict = intervalVerdict(diff.matched, diff.matched + diff.unknown, probe.matched);
|
|
413
|
+
return evidenceResult(
|
|
414
|
+
verdict,
|
|
415
|
+
`蓝图「${probe.key}」现场对上 ${diff.matched}/${diff.total} 格,${diff.unknown} 格没加载(要 ${compareText(probe.matched)})`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
if (probe.source === 'position') {
|
|
419
|
+
if (probe.region.dimension !== ctx.dimension) {
|
|
420
|
+
return evidenceResult('unknown', `人在 ${ctx.dimension},里程碑区域在 ${probe.region.dimension}`);
|
|
421
|
+
}
|
|
422
|
+
const position = ctx.position();
|
|
423
|
+
const hit = inRegion(position, probe.region);
|
|
424
|
+
return evidenceResult(hit ? 'ok' : 'bad', `位置 (${position.map((n) => Math.round(n)).join(', ')}) ${hit ? '在' : '不在'}指定区域`);
|
|
425
|
+
}
|
|
426
|
+
if (probe.source === 'status') {
|
|
427
|
+
const actual = ctx.status()[probe.field];
|
|
428
|
+
const hit = typeof probe.equals === 'boolean'
|
|
429
|
+
? actual === probe.equals
|
|
430
|
+
: typeof actual === 'number' && compare(actual, probe.equals);
|
|
431
|
+
return evidenceResult(hit ? 'ok' : 'bad', `${probe.field}=${String(actual)}(要 ${typeof probe.equals === 'boolean' ? String(probe.equals) : compareText(probe.equals)})`);
|
|
432
|
+
}
|
|
433
|
+
const region = probe.region;
|
|
434
|
+
if (region.dimension !== ctx.dimension) {
|
|
435
|
+
return evidenceResult('unknown', `当前在 ${ctx.dimension},计数区域在 ${region.dimension}`);
|
|
436
|
+
}
|
|
437
|
+
if (!regionObservable(region, ctx.world)) {
|
|
438
|
+
return evidenceResult('unknown', '计数区域没有完整加载,当前实体表不能证明这里的总数');
|
|
439
|
+
}
|
|
440
|
+
if (probe.source === 'entity') {
|
|
441
|
+
const actual = ctx.entities().filter((entity) => entity.type === probe.type && inRegion(entity.position, region)).length;
|
|
442
|
+
return evidenceResult(compare(actual, probe.count) ? 'ok' : 'bad', `区域内 ${probe.type}=${actual}(要 ${compareText(probe.count)})`);
|
|
443
|
+
}
|
|
444
|
+
const actual = ctx.items()
|
|
445
|
+
.filter((item) => item.item === probe.item && inRegion(item.position, region))
|
|
446
|
+
.reduce((sum, item) => sum + item.count, 0);
|
|
447
|
+
return evidenceResult(compare(actual, probe.count) ? 'ok' : 'bad', `区域内掉落 ${probe.item}=${actual}(要 ${compareText(probe.count)})`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function evaluateGoalStep(step: GoalPlanStep, ctx: GoalProbeContext): GoalEvidence {
|
|
451
|
+
const results = step.verify?.map((probe) => evaluateProbe(probe, ctx)) ?? [];
|
|
452
|
+
const verdict: CheckVerdict = results.some((result) => result.verdict === 'error') ? 'error'
|
|
453
|
+
: results.some((result) => result.verdict === 'bad') ? 'bad'
|
|
454
|
+
: results.some((result) => result.verdict === 'unknown') ? 'unknown'
|
|
455
|
+
: 'ok';
|
|
456
|
+
return {
|
|
457
|
+
observedAt: ctx.observedAt,
|
|
458
|
+
connectionGeneration: ctx.connectionGeneration,
|
|
459
|
+
realm: ctx.realm,
|
|
460
|
+
dimension: ctx.dimension,
|
|
461
|
+
verdict,
|
|
462
|
+
results,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function completedGoalSteps(plan: GoalPlan): number {
|
|
467
|
+
const first = plan.steps.findIndex((step) => step.state === 'pending');
|
|
468
|
+
return first < 0 ? plan.steps.length : first;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export function goalPlanSummary(plan: GoalPlan): { completed: number; total: number; next: GoalPlanStep | null } {
|
|
472
|
+
const completed = completedGoalSteps(plan);
|
|
473
|
+
return { completed, total: plan.steps.length, next: plan.steps[completed] ?? null };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function latestMechanicalFrontier(plan: GoalPlan): number | null {
|
|
477
|
+
const completed = completedGoalSteps(plan);
|
|
478
|
+
const index = completed - 1;
|
|
479
|
+
return index >= 0 && plan.steps[index].verify ? index : null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function evidenceText(evidence: GoalEvidence): string[] {
|
|
483
|
+
return evidence.results.map((result) => result.text);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export function coordinateGoalPlan(plan: GoalPlan, ctx: GoalProbeContext): GoalPlanCoordination {
|
|
487
|
+
const transitions: GoalPlanTransition[] = [];
|
|
488
|
+
const before = completedGoalSteps(plan);
|
|
489
|
+
const sampled = new Map<number, GoalEvidence>();
|
|
490
|
+
let cursor = before;
|
|
491
|
+
while (cursor < plan.steps.length) {
|
|
492
|
+
const step = plan.steps[cursor];
|
|
493
|
+
if (!step.verify) break;
|
|
494
|
+
const evidence = evaluateGoalStep(step, ctx);
|
|
495
|
+
sampled.set(cursor, evidence);
|
|
496
|
+
step.evidence = evidence;
|
|
497
|
+
if (evidence.verdict !== 'ok') break;
|
|
498
|
+
step.state = 'verified';
|
|
499
|
+
step.completedAt = ctx.observedAt;
|
|
500
|
+
cursor++;
|
|
501
|
+
}
|
|
502
|
+
const advanced = completedGoalSteps(plan);
|
|
503
|
+
if (advanced > before) {
|
|
504
|
+
const evidence: string[] = [];
|
|
505
|
+
for (let index = before; index < advanced; index++) {
|
|
506
|
+
const sampledEvidence = sampled.get(index);
|
|
507
|
+
if (sampledEvidence) evidence.push(...evidenceText(sampledEvidence));
|
|
508
|
+
}
|
|
509
|
+
transitions.push({
|
|
510
|
+
kind: 'advance', from: before, to: advanced, total: plan.steps.length,
|
|
511
|
+
next: plan.steps[advanced]?.do ?? null, evidence,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
let blocker: GoalEvidence | null = null;
|
|
516
|
+
const frontier = latestMechanicalFrontier(plan);
|
|
517
|
+
if (frontier !== null) {
|
|
518
|
+
const step = plan.steps[frontier];
|
|
519
|
+
const evidence = sampled.get(frontier) ?? evaluateGoalStep(step, ctx);
|
|
520
|
+
step.evidence = evidence;
|
|
521
|
+
if (evidence.verdict === 'bad') {
|
|
522
|
+
const from = completedGoalSteps(plan);
|
|
523
|
+
step.state = 'pending';
|
|
524
|
+
step.completedAt = null;
|
|
525
|
+
for (let index = frontier + 1; index < plan.steps.length; index++) {
|
|
526
|
+
if (plan.steps[index].state === 'verified') {
|
|
527
|
+
plan.steps[index].state = 'pending';
|
|
528
|
+
plan.steps[index].completedAt = null;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const to = completedGoalSteps(plan);
|
|
532
|
+
plan.readyAnnounced = false;
|
|
533
|
+
transitions.push({
|
|
534
|
+
kind: 'regress', from, to, total: plan.steps.length, step: frontier + 1,
|
|
535
|
+
evidence: evidenceText(evidence),
|
|
536
|
+
});
|
|
537
|
+
} else if (evidence.verdict === 'unknown' || evidence.verdict === 'error') {
|
|
538
|
+
blocker = evidence;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const summary = goalPlanSummary(plan);
|
|
543
|
+
const ready = summary.completed === summary.total && blocker === null;
|
|
544
|
+
if (!ready) plan.readyAnnounced = false;
|
|
545
|
+
if (ready && !plan.readyAnnounced) {
|
|
546
|
+
plan.readyAnnounced = true;
|
|
547
|
+
transitions.push({ kind: 'ready', total: summary.total });
|
|
548
|
+
}
|
|
549
|
+
return { transitions, ...summary, ready, blocker };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** 一句话说清这一步登记的机械旁证要什么。只复述登记内容,不加判断。 */
|
|
553
|
+
function describeProbes(probes: GoalProbe[] | null): string {
|
|
554
|
+
if (!probes || probes.length === 0) return '没有登记旁证';
|
|
555
|
+
return probes.map((p) => {
|
|
556
|
+
switch (p.source) {
|
|
557
|
+
case 'block': return `${p.at.join(',')} 那一格是 ${p.is}`;
|
|
558
|
+
case 'inventory': return `包里 ${p.item} ${compareText(p.count)}`;
|
|
559
|
+
case 'blueprint': return `蓝图 ${p.key}`;
|
|
560
|
+
case 'position': return '人在指定区域里';
|
|
561
|
+
case 'entity': return `区域内 ${p.type} ${compareText(p.count)}`;
|
|
562
|
+
case 'item': return `区域内掉落物 ${p.item} ${compareText(p.count)}`;
|
|
563
|
+
default: return `状态 ${p.field}`;
|
|
564
|
+
}
|
|
565
|
+
}).join('、');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export function recordGoalJudgment(
|
|
569
|
+
plan: GoalPlan,
|
|
570
|
+
stepNumber: number,
|
|
571
|
+
note: string,
|
|
572
|
+
at: number,
|
|
573
|
+
): { ok: true; completed: number; total: number; next: GoalPlanStep | null } | { ok: false; error: string } {
|
|
574
|
+
const index = stepNumber - 1;
|
|
575
|
+
const step = plan.steps[index];
|
|
576
|
+
if (!step) return { ok: false, error: `没有第 ${stepNumber} 个里程碑` };
|
|
577
|
+
if (!step.judgment) {
|
|
578
|
+
// 回执说明已登记的目标类型及其自动验收方式。
|
|
579
|
+
const what = describeProbes(step.verify);
|
|
580
|
+
const nextJudgment = plan.steps.findIndex((s, i) => i >= index && s.judgment !== null);
|
|
581
|
+
return {
|
|
582
|
+
ok: false,
|
|
583
|
+
error: `第 ${stepNumber} 步登记的是机械核验(${what}),它会自己判定,不用也不能用判断代填`
|
|
584
|
+
+ (nextJudgment >= 0 ? `;能登记判断的是第 ${nextJudgment + 1} 步` : ';这条目标里没有判断型里程碑'),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const trimmed = note.trim();
|
|
588
|
+
if (!trimmed) return { ok: false, error: 'judgment 说明不能空' };
|
|
589
|
+
const completed = completedGoalSteps(plan);
|
|
590
|
+
if (completed !== index) {
|
|
591
|
+
const blocker = plan.steps[completed];
|
|
592
|
+
return {
|
|
593
|
+
ok: false,
|
|
594
|
+
error: `当前应处理第 ${completed + 1} 步,不能越过前面的里程碑`
|
|
595
|
+
+ (blocker ? `;第 ${completed + 1} 步是「${blocker.do}」`
|
|
596
|
+
+ `(${blocker.judgment ? '判断型,登记它就过' : `机械核验:${describeProbes(blocker.verify)}`})` : ''),
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
step.state = 'judged';
|
|
600
|
+
step.evidence = { judgedAt: at, note: trimmed };
|
|
601
|
+
step.completedAt = at;
|
|
602
|
+
plan.readyAnnounced = false;
|
|
603
|
+
return { ok: true, ...goalPlanSummary(plan) };
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export function reopenGoalJudgment(
|
|
607
|
+
plan: GoalPlan,
|
|
608
|
+
stepNumber: number,
|
|
609
|
+
): { ok: true; completed: number; total: number; next: GoalPlanStep | null } | { ok: false; error: string } {
|
|
610
|
+
const step = plan.steps[stepNumber - 1];
|
|
611
|
+
if (!step) return { ok: false, error: `没有第 ${stepNumber} 个里程碑` };
|
|
612
|
+
if (!step.judgment) return { ok: false, error: `第 ${stepNumber} 步不是判断里程碑` };
|
|
613
|
+
if (step.state !== 'judged') return { ok: false, error: `第 ${stepNumber} 步还没有判断记录` };
|
|
614
|
+
step.state = 'pending';
|
|
615
|
+
step.evidence = null;
|
|
616
|
+
step.completedAt = null;
|
|
617
|
+
plan.readyAnnounced = false;
|
|
618
|
+
return { ok: true, ...goalPlanSummary(plan) };
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export function goalPlanDoneIssue(plan: GoalPlan): string | null {
|
|
622
|
+
const summary = goalPlanSummary(plan);
|
|
623
|
+
if (summary.completed < summary.total) {
|
|
624
|
+
return `只有 ${summary.completed}/${summary.total} 项里程碑完成;下一步 #${summary.completed + 1} ${summary.next?.do}`;
|
|
625
|
+
}
|
|
626
|
+
const frontier = latestMechanicalFrontier(plan);
|
|
627
|
+
if (frontier !== null) {
|
|
628
|
+
const evidence = plan.steps[frontier].evidence;
|
|
629
|
+
if (!evidence || !('verdict' in evidence) || evidence.verdict !== 'ok') {
|
|
630
|
+
const detail = evidence && 'results' in evidence ? evidence.results.map((result) => result.text).join(';') : '没有新鲜旁证';
|
|
631
|
+
return `最后一项机械旁证还不能结案:${detail}`;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export function renderGoalPlanTransition(transition: GoalPlanTransition): string {
|
|
638
|
+
if (transition.kind === 'advance') {
|
|
639
|
+
const evidence = transition.evidence.length > 0 ? `:${transition.evidence.join(';')}` : '';
|
|
640
|
+
const next = transition.next ? `;下一步 ${transition.next}` : '';
|
|
641
|
+
return `机械旁证推进到 ${transition.to}/${transition.total}${evidence}${next}`;
|
|
642
|
+
}
|
|
643
|
+
if (transition.kind === 'regress') {
|
|
644
|
+
return `第 ${transition.step}/${transition.total} 项旁证已失效:${transition.evidence.join(';')};进度退回 ${transition.to}/${transition.total}`;
|
|
645
|
+
}
|
|
646
|
+
return `${transition.total} 项里程碑都有旁证,可以用 mc_goal done 明确结案`;
|
|
647
|
+
}
|