deepclause-sdk 0.0.88 → 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 +418 -914
- package/dist/agent.d.ts +19 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +221 -4
- package/dist/agent.js.map +1 -1
- package/dist/cli/config.d.ts +323 -176
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +146 -188
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +61 -425
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/login.d.ts +29 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/login.js +184 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/readline.d.ts +6 -0
- package/dist/cli/readline.d.ts.map +1 -0
- package/dist/cli/readline.js +15 -0
- package/dist/cli/readline.js.map +1 -0
- package/dist/cli/repl-bin.d.ts +9 -0
- package/dist/cli/repl-bin.d.ts.map +1 -0
- package/dist/cli/repl-bin.js +16 -0
- package/dist/cli/repl-bin.js.map +1 -0
- package/dist/cli/repl.d.ts +191 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +1232 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli/run.d.ts +17 -18
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +196 -183
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/set-backend.d.ts +53 -0
- package/dist/cli/set-backend.d.ts.map +1 -0
- package/dist/cli/set-backend.js +259 -0
- package/dist/cli/set-backend.js.map +1 -0
- package/dist/cli/visualize.d.ts +38 -0
- package/dist/cli/visualize.d.ts.map +1 -0
- package/dist/cli/visualize.js +125 -0
- package/dist/cli/visualize.js.map +1 -0
- package/dist/compiler.d.ts +5 -52
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +4 -768
- package/dist/compiler.js.map +1 -1
- package/dist/index.d.ts +29 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/judge/index.d.ts +9 -0
- package/dist/judge/index.d.ts.map +1 -0
- package/dist/judge/index.js +6 -0
- package/dist/judge/index.js.map +1 -0
- package/dist/judge/jev.d.ts +21 -0
- package/dist/judge/jev.d.ts.map +1 -0
- package/dist/judge/jev.js +218 -0
- package/dist/judge/jev.js.map +1 -0
- package/dist/judge/llm.d.ts +23 -0
- package/dist/judge/llm.d.ts.map +1 -0
- package/dist/judge/llm.js +120 -0
- package/dist/judge/llm.js.map +1 -0
- package/dist/judge/marshal.d.ts +26 -0
- package/dist/judge/marshal.d.ts.map +1 -0
- package/dist/judge/marshal.js +266 -0
- package/dist/judge/marshal.js.map +1 -0
- package/dist/judge/mock.d.ts +28 -0
- package/dist/judge/mock.d.ts.map +1 -0
- package/dist/judge/mock.js +98 -0
- package/dist/judge/mock.js.map +1 -0
- package/dist/judge/types.d.ts +95 -0
- package/dist/judge/types.d.ts.map +1 -0
- package/dist/judge/types.js +23 -0
- package/dist/judge/types.js.map +1 -0
- package/dist/prolog/bridge.d.ts +5 -1
- package/dist/prolog/bridge.d.ts.map +1 -1
- package/dist/prolog/bridge.js +29 -1
- package/dist/prolog/bridge.js.map +1 -1
- package/dist/prolog-src/deepclause_analysis.pl +10 -2
- package/dist/prolog-src/deepclause_mi.pl +2522 -118
- package/dist/repl/agent.d.ts +72 -0
- package/dist/repl/agent.d.ts.map +1 -0
- package/dist/repl/agent.js +366 -0
- package/dist/repl/agent.js.map +1 -0
- package/dist/repl/auth.d.ts +45 -0
- package/dist/repl/auth.d.ts.map +1 -0
- package/dist/repl/auth.js +116 -0
- package/dist/repl/auth.js.map +1 -0
- package/dist/repl/backend.d.ts +290 -0
- package/dist/repl/backend.d.ts.map +1 -0
- package/dist/repl/backend.js +370 -0
- package/dist/repl/backend.js.map +1 -0
- package/dist/repl/compaction.d.ts +122 -0
- package/dist/repl/compaction.d.ts.map +1 -0
- package/dist/repl/compaction.js +318 -0
- package/dist/repl/compaction.js.map +1 -0
- package/dist/repl/export.d.ts +30 -0
- package/dist/repl/export.d.ts.map +1 -0
- package/dist/repl/export.js +23 -0
- package/dist/repl/export.js.map +1 -0
- package/dist/repl/frames.d.ts +21 -0
- package/dist/repl/frames.d.ts.map +1 -0
- package/dist/repl/frames.js +79 -0
- package/dist/repl/frames.js.map +1 -0
- package/dist/repl/harness-tools.d.ts +74 -0
- package/dist/repl/harness-tools.d.ts.map +1 -0
- package/dist/repl/harness-tools.js +121 -0
- package/dist/repl/harness-tools.js.map +1 -0
- package/dist/repl/highlight.d.ts +30 -0
- package/dist/repl/highlight.d.ts.map +1 -0
- package/dist/repl/highlight.js +285 -0
- package/dist/repl/highlight.js.map +1 -0
- package/dist/repl/history.d.ts +234 -0
- package/dist/repl/history.d.ts.map +1 -0
- package/dist/repl/history.js +661 -0
- package/dist/repl/history.js.map +1 -0
- package/dist/repl/index.d.ts +120 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/repl/index.js +107 -0
- package/dist/repl/index.js.map +1 -0
- package/dist/repl/judge.d.ts +51 -0
- package/dist/repl/judge.d.ts.map +1 -0
- package/dist/repl/judge.js +58 -0
- package/dist/repl/judge.js.map +1 -0
- package/dist/repl/markdown.d.ts +25 -0
- package/dist/repl/markdown.d.ts.map +1 -0
- package/dist/repl/markdown.js +103 -0
- package/dist/repl/markdown.js.map +1 -0
- package/dist/repl/picker.d.ts +34 -0
- package/dist/repl/picker.d.ts.map +1 -0
- package/dist/repl/picker.js +81 -0
- package/dist/repl/picker.js.map +1 -0
- package/dist/repl/project-context.d.ts +16 -0
- package/dist/repl/project-context.d.ts.map +1 -0
- package/dist/repl/project-context.js +23 -0
- package/dist/repl/project-context.js.map +1 -0
- package/dist/repl/render.d.ts +160 -0
- package/dist/repl/render.d.ts.map +1 -0
- package/dist/repl/render.js +715 -0
- package/dist/repl/render.js.map +1 -0
- package/dist/repl/session.d.ts +356 -0
- package/dist/repl/session.d.ts.map +1 -0
- package/dist/repl/session.js +1275 -0
- package/dist/repl/session.js.map +1 -0
- package/dist/repl/status.d.ts +100 -0
- package/dist/repl/status.d.ts.map +1 -0
- package/dist/repl/status.js +218 -0
- package/dist/repl/status.js.map +1 -0
- package/dist/repl/tools.d.ts +53 -0
- package/dist/repl/tools.d.ts.map +1 -0
- package/dist/repl/tools.js +307 -0
- package/dist/repl/tools.js.map +1 -0
- package/dist/repl/toolset.d.ts +32 -0
- package/dist/repl/toolset.d.ts.map +1 -0
- package/dist/repl/toolset.js +82 -0
- package/dist/repl/toolset.js.map +1 -0
- package/dist/repl/tui/editor.d.ts +17 -0
- package/dist/repl/tui/editor.d.ts.map +1 -0
- package/dist/repl/tui/editor.js +39 -0
- package/dist/repl/tui/editor.js.map +1 -0
- package/dist/repl/tui/status-bar.d.ts +26 -0
- package/dist/repl/tui/status-bar.d.ts.map +1 -0
- package/dist/repl/tui/status-bar.js +47 -0
- package/dist/repl/tui/status-bar.js.map +1 -0
- package/dist/repl/tui/theme.d.ts +30 -0
- package/dist/repl/tui/theme.d.ts.map +1 -0
- package/dist/repl/tui/theme.js +81 -0
- package/dist/repl/tui/theme.js.map +1 -0
- package/dist/repl/tui/transcript.d.ts +39 -0
- package/dist/repl/tui/transcript.d.ts.map +1 -0
- package/dist/repl/tui/transcript.js +123 -0
- package/dist/repl/tui/transcript.js.map +1 -0
- package/dist/runner.d.ts +145 -13
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +600 -282
- package/dist/runner.js.map +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +56 -32
- package/dist/sdk.js.map +1 -1
- package/dist/system/assets/docs/AGENT_PROMPT.md +1373 -0
- package/dist/system/assets/docs/DML_AUTHORING.md +195 -0
- package/dist/system/assets/docs/DML_REFERENCE.md +393 -72
- package/dist/system/assets/docs/HARNESS_EXAMPLES.md +172 -0
- package/dist/system/assets/docs/HARNESS_PATTERNS.md +562 -0
- package/dist/system/assets/docs/META_PROMPT.md +68 -0
- package/dist/system/assets/docs/PROLOG_SNIPPETS.md +117 -0
- package/dist/system/assets/index.d.ts +9 -17
- package/dist/system/assets/index.d.ts.map +1 -1
- package/dist/system/assets/index.js +26 -85
- package/dist/system/assets/index.js.map +1 -1
- package/dist/system/runtime/agentvm-manager.d.ts +9 -2
- package/dist/system/runtime/agentvm-manager.d.ts.map +1 -1
- package/dist/system/runtime/agentvm-manager.js +70 -44
- package/dist/system/runtime/agentvm-manager.js.map +1 -1
- package/dist/system/runtime/media.d.ts +23 -0
- package/dist/system/runtime/media.d.ts.map +1 -0
- package/dist/system/runtime/media.js +55 -0
- package/dist/system/runtime/media.js.map +1 -0
- package/dist/system/runtime/shell-limits.d.ts +21 -0
- package/dist/system/runtime/shell-limits.d.ts.map +1 -0
- package/dist/system/runtime/shell-limits.js +39 -0
- package/dist/system/runtime/shell-limits.js.map +1 -0
- package/dist/system/runtime/shell-manager.d.ts +8 -3
- package/dist/system/runtime/shell-manager.d.ts.map +1 -1
- package/dist/system/runtime/shell-manager.js +56 -39
- package/dist/system/runtime/shell-manager.js.map +1 -1
- package/dist/system/runtime/shell-tool-events.js +5 -0
- package/dist/system/runtime/shell-tool-events.js.map +1 -1
- package/dist/system/runtime/token-usage.d.ts +2 -0
- package/dist/system/runtime/token-usage.d.ts.map +1 -1
- package/dist/system/runtime/token-usage.js +7 -0
- package/dist/system/runtime/token-usage.js.map +1 -1
- package/dist/tools.d.ts +3 -21
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +14 -168
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +149 -118
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +19 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +338 -0
- package/dist/validate.js.map +1 -0
- package/dist/visualize/index.d.ts +52 -0
- package/dist/visualize/index.d.ts.map +1 -0
- package/dist/visualize/index.js +189 -0
- package/dist/visualize/index.js.map +1 -0
- package/package.json +17 -15
- package/src/prolog-src/deepclause_analysis.pl +10 -2
- package/src/prolog-src/deepclause_mi.pl +2522 -118
- package/dist/cli/commands.d.ts +0 -42
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -246
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/compile.d.ts +0 -99
- package/dist/cli/compile.d.ts.map +0 -1
- package/dist/cli/compile.js +0 -363
- package/dist/cli/compile.js.map +0 -1
- package/dist/cli/default-skills.d.ts +0 -10
- package/dist/cli/default-skills.d.ts.map +0 -1
- package/dist/cli/default-skills.js +0 -206
- package/dist/cli/default-skills.js.map +0 -1
- package/dist/cli/interactive.d.ts +0 -2
- package/dist/cli/interactive.d.ts.map +0 -1
- package/dist/cli/interactive.js +0 -20
- package/dist/cli/interactive.js.map +0 -1
- package/dist/cli/mcp.d.ts +0 -56
- package/dist/cli/mcp.d.ts.map +0 -1
- package/dist/cli/mcp.js +0 -138
- package/dist/cli/mcp.js.map +0 -1
- package/dist/cli/prompt.d.ts +0 -2
- package/dist/cli/prompt.d.ts.map +0 -1
- package/dist/cli/prompt.js +0 -2
- package/dist/cli/prompt.js.map +0 -1
- package/dist/cli/search.d.ts +0 -19
- package/dist/cli/search.d.ts.map +0 -1
- package/dist/cli/search.js +0 -189
- package/dist/cli/search.js.map +0 -1
- package/dist/cli/tool-args.d.ts +0 -2
- package/dist/cli/tool-args.d.ts.map +0 -1
- package/dist/cli/tool-args.js +0 -39
- package/dist/cli/tool-args.js.map +0 -1
- package/dist/cli/tools.d.ts +0 -36
- package/dist/cli/tools.d.ts.map +0 -1
- package/dist/cli/tools.js +0 -229
- package/dist/cli/tools.js.map +0 -1
- package/dist/cli/tui/index.d.ts +0 -22
- package/dist/cli/tui/index.d.ts.map +0 -1
- package/dist/cli/tui/index.js +0 -29
- package/dist/cli/tui/index.js.map +0 -1
- package/dist/cli/tui.d.ts +0 -167
- package/dist/cli/tui.d.ts.map +0 -1
- package/dist/cli/tui.js +0 -5228
- package/dist/cli/tui.js.map +0 -1
- package/dist/compiler_prompt.d.ts +0 -20
- package/dist/compiler_prompt.d.ts.map +0 -1
- package/dist/compiler_prompt.js +0 -870
- package/dist/compiler_prompt.js.map +0 -1
- package/dist/deepclause-cli.skill +0 -0
- package/dist/prolog-src/deepclause_mi.pl.bak +0 -570
- package/dist/system/assets/compactors/default-memory-compactor.dml +0 -6
- package/dist/system/assets/docs/CONDUCTOR_PROMPT.md +0 -105
- package/dist/system/assets/docs/DML_COMPILER_PROMPT.md +0 -700
- package/dist/system/assets/docs/TUI.md +0 -150
- package/dist/system/assets/recipes/deepclause-coding-workflow/SKILL.md +0 -100
- package/dist/system/assets/skills/conductor.dml +0 -76
- package/dist/system/assets/skills/deep-planner.dml +0 -412
- package/dist/system/assets/skills/plan.dml +0 -429
- package/dist/system/assets/skills/security-planner.dml +0 -337
- package/dist/system/assets/skills/skill-creator.dml +0 -187
- package/dist/system/runtime/catalog-recipes.d.ts +0 -24
- package/dist/system/runtime/catalog-recipes.d.ts.map +0 -1
- package/dist/system/runtime/catalog-recipes.js +0 -273
- package/dist/system/runtime/catalog-recipes.js.map +0 -1
- package/dist/system/runtime/catalog-skills.d.ts +0 -50
- package/dist/system/runtime/catalog-skills.d.ts.map +0 -1
- package/dist/system/runtime/catalog-skills.js +0 -152
- package/dist/system/runtime/catalog-skills.js.map +0 -1
- package/dist/system/runtime/conductor.d.ts +0 -105
- package/dist/system/runtime/conductor.d.ts.map +0 -1
- package/dist/system/runtime/conductor.js +0 -732
- package/dist/system/runtime/conductor.js.map +0 -1
- package/dist/system/runtime/console-capture.d.ts +0 -9
- package/dist/system/runtime/console-capture.d.ts.map +0 -1
- package/dist/system/runtime/console-capture.js +0 -67
- package/dist/system/runtime/console-capture.js.map +0 -1
- package/dist/system/runtime/dml-executor.d.ts +0 -52
- package/dist/system/runtime/dml-executor.d.ts.map +0 -1
- package/dist/system/runtime/dml-executor.js +0 -203
- package/dist/system/runtime/dml-executor.js.map +0 -1
- package/dist/system/runtime/runtime-tools.d.ts +0 -31
- package/dist/system/runtime/runtime-tools.d.ts.map +0 -1
- package/dist/system/runtime/runtime-tools.js +0 -312
- package/dist/system/runtime/runtime-tools.js.map +0 -1
- package/dist/system/runtime/skill-creator.d.ts +0 -38
- package/dist/system/runtime/skill-creator.d.ts.map +0 -1
- package/dist/system/runtime/skill-creator.js +0 -763
- package/dist/system/runtime/skill-creator.js.map +0 -1
- package/src/prolog-src/deepclause_mi.pl.bak +0 -570
|
@@ -20,11 +20,78 @@
|
|
|
20
20
|
create_engine/6,
|
|
21
21
|
step_engine/4,
|
|
22
22
|
destroy_engine/1,
|
|
23
|
+
% Frame predicates (M0: reentrant, frame-aware execution)
|
|
24
|
+
create_frame/6,
|
|
25
|
+
create_frame_raw/5,
|
|
26
|
+
step_frame/4,
|
|
27
|
+
end_frame/1,
|
|
28
|
+
frame_info/4,
|
|
29
|
+
default_frame/2,
|
|
30
|
+
% REPL session and query mode (M1)
|
|
31
|
+
create_session/4,
|
|
32
|
+
clear_session/1,
|
|
33
|
+
destroy_session/1,
|
|
34
|
+
load_code/3,
|
|
35
|
+
repl_initial_state/3,
|
|
36
|
+
query_start/5,
|
|
37
|
+
repl_solve/4,
|
|
38
|
+
collect_bindings/2,
|
|
39
|
+
repl_listing/2,
|
|
40
|
+
repl_listing/3,
|
|
41
|
+
repl_reserved/1,
|
|
42
|
+
% Freeze lock and export (M2)
|
|
43
|
+
repl_freeze/1,
|
|
44
|
+
repl_unfreeze/1,
|
|
45
|
+
repl_lock/1,
|
|
46
|
+
repl_is_frozen/1,
|
|
47
|
+
repl_try_unfreeze/2,
|
|
48
|
+
export_harness/3,
|
|
49
|
+
export_stats/4,
|
|
50
|
+
repl_script_files/2,
|
|
51
|
+
repl_materialize_embedded/1,
|
|
52
|
+
% Prompt mode (M3)
|
|
53
|
+
repl_meta_prompt/2,
|
|
54
|
+
repl_meta_prompt/3,
|
|
55
|
+
repl_extract_code/2,
|
|
56
|
+
repl_assert/3,
|
|
57
|
+
repl_assert_many/3,
|
|
58
|
+
repl_retract/3,
|
|
59
|
+
repl_define/4,
|
|
60
|
+
repl_delete_predicate/4,
|
|
61
|
+
repl_edit_touched/2,
|
|
62
|
+
repl_edit_touched_text/2,
|
|
63
|
+
repl_edit_touched_clear/1,
|
|
64
|
+
repl_set_dml_reference/2,
|
|
65
|
+
repl_set_authoring_examples/2,
|
|
66
|
+
repl_manifest/2,
|
|
67
|
+
repl_history_load/2,
|
|
68
|
+
repl_history_record/2,
|
|
69
|
+
repl_history_summary/2,
|
|
70
|
+
repl_rollback/3,
|
|
71
|
+
repl_user_predicates/2,
|
|
72
|
+
repl_user_predicates_text/2,
|
|
73
|
+
repl_predicate_arities/3,
|
|
74
|
+
repl_set_meta_catalog/2,
|
|
75
|
+
repl_set_meta_template/2,
|
|
76
|
+
repl_set_harness_examples/2,
|
|
77
|
+
repl_set_prolog_snippets/2,
|
|
78
|
+
repl_run_meta/4,
|
|
79
|
+
repl_snapshot_save/1,
|
|
80
|
+
repl_snapshot_restore/1,
|
|
81
|
+
repl_commit/3,
|
|
82
|
+
repl_last_commit_summary/2,
|
|
83
|
+
repl_execute_frame/4,
|
|
84
|
+
repl_record_meta/3,
|
|
85
|
+
repl_memory_term/3,
|
|
86
|
+
repl_get_memory/3,
|
|
87
|
+
repl_set_memory/3,
|
|
23
88
|
post_agent_result/4,
|
|
24
89
|
post_exec_result/3,
|
|
25
90
|
post_sample_token_result/3,
|
|
26
91
|
post_llm_result/3,
|
|
92
|
+
post_judgment_result/3,
|
|
27
93
|
provide_input/2,
|
|
94
|
+
provide_input_cancelled/1,
|
|
28
95
|
mi/3,
|
|
29
96
|
% Tool engine predicates for isolated tool execution
|
|
30
97
|
create_tool_engine/4,
|
|
@@ -41,7 +108,25 @@
|
|
|
41
108
|
:- discontiguous var_to_name/3.
|
|
42
109
|
|
|
43
110
|
%% Dynamic predicates for session state (engine management only, not memory!)
|
|
44
|
-
:- dynamic session_engine/2. % session_engine(SessionId, Engine)
|
|
111
|
+
:- dynamic session_engine/2. % session_engine(SessionId, Engine) - legacy default engine handle
|
|
112
|
+
:- dynamic session_frame/4. % session_frame(FrameId, SessionId, Kind, Depth)
|
|
113
|
+
:- dynamic session_frame_engine/2. % session_frame_engine(FrameId, Engine)
|
|
114
|
+
:- dynamic session_default_frame/2. % session_default_frame(SessionId, FrameId)
|
|
115
|
+
:- dynamic session_repl_memory/2. % session_repl_memory(SessionId, HarnessMemory) - object/entry memory
|
|
116
|
+
:- dynamic session_meta_memory/2. % session_meta_memory(SessionId, MetaMemory) - code-writing task memory
|
|
117
|
+
:- dynamic session_repl_mode/2. % session_repl_mode(SessionId, true/false) - gates format/writeln output shims
|
|
118
|
+
:- dynamic session_frozen/2. % session_frozen(SessionId, true/false) - read-only lock
|
|
119
|
+
:- dynamic session_locked/2. % session_locked(SessionId, true/false) - irreversibly frozen (--locked)
|
|
120
|
+
:- dynamic session_directive/2. % session_directive(SessionId, Directive) - recorded :- directives for export
|
|
121
|
+
:- dynamic session_meta_catalog/2. % session_meta_catalog(SessionId, CatalogText) - host tool catalog for the meta prompt
|
|
122
|
+
:- dynamic session_dml_reference/2. % session_dml_reference(SessionId, Text) - bundled DML reference for the meta prompt
|
|
123
|
+
:- dynamic session_authoring_examples/2. % session_authoring_examples(SessionId, Text) - curated authoring patterns
|
|
124
|
+
:- dynamic session_meta_template/2. % session_meta_template(SessionId, Template) - bundled META_PROMPT.md template
|
|
125
|
+
:- dynamic session_harness_examples/2. % session_harness_examples(SessionId, Text) - bundled HARNESS_EXAMPLES.md
|
|
126
|
+
:- dynamic session_prolog_snippets/2. % session_prolog_snippets(SessionId, Text) - bundled PROLOG_SNIPPETS.md
|
|
127
|
+
:- dynamic session_last_commit/2. % session_last_commit(SessionId, [Name/Arity]) - predicates the last commit replaced
|
|
128
|
+
:- dynamic session_edit_touched/2. % session_edit_touched(SessionId, Name/Arity) - agent-mode edit transactions
|
|
129
|
+
:- dynamic repl_snapshot/2. % repl_snapshot(SessionId, Snapshot) - transactional edit snapshot
|
|
45
130
|
:- dynamic session_params/2. % session_params(SessionId, ParamsDict)
|
|
46
131
|
:- dynamic session_user_tools/2. % session_user_tools(SessionId, ToolName)
|
|
47
132
|
:- dynamic session_user_tool_schema/3. % session_user_tool_schema(SessionId, ToolName, Schema)
|
|
@@ -50,10 +135,12 @@
|
|
|
50
135
|
:- dynamic session_exec_result/2. % session_exec_result(SessionId, Result)
|
|
51
136
|
:- dynamic session_sample_token_result/2. % session_sample_token_result(SessionId, Result)
|
|
52
137
|
:- dynamic session_llm_result/2. % session_llm_result(SessionId, Result)
|
|
138
|
+
:- dynamic session_judgment_result/2. % session_judgment_result(SessionId, Result)
|
|
53
139
|
:- dynamic session_trace_log/2. % session_trace_log(SessionId, TraceLog) - accumulated trace entries
|
|
54
140
|
:- dynamic session_trace_enabled/2. % session_trace_enabled(SessionId, true/false)
|
|
55
141
|
:- dynamic session_pending_signal/2. % session_pending_signal(SessionId, Signal) - fallback for engine_fetch
|
|
56
142
|
:- dynamic session_user_input/2. % session_user_input(SessionId, Input) - for ask_user tool
|
|
143
|
+
:- dynamic session_input_cancelled/1. % session_input_cancelled(SessionId) - input/2 should fail (non-interactive)
|
|
57
144
|
|
|
58
145
|
%% ============================================================
|
|
59
146
|
%% DML Parsing
|
|
@@ -258,6 +345,182 @@ wrapper_type(list, array).
|
|
|
258
345
|
wrapper_type(object, object).
|
|
259
346
|
wrapper_type(dict, object).
|
|
260
347
|
|
|
348
|
+
%% ============================================================
|
|
349
|
+
%% Source-facing view of user clauses
|
|
350
|
+
%%
|
|
351
|
+
%% At load time task/N and prompt/N are rewritten to task_named/3 and
|
|
352
|
+
%% prompt_named/3, and interpolated descriptions are hoisted into a preceding
|
|
353
|
+
%% format/3. Those internal forms must not leak into listing/export — the meta
|
|
354
|
+
%% task only knows task/N — so reverse them for display only.
|
|
355
|
+
%% ============================================================
|
|
356
|
+
|
|
357
|
+
%% clean_clause_body(+Body, -CleanBody)
|
|
358
|
+
clean_clause_body(Body, Clean) :-
|
|
359
|
+
conj_list(Body, Goals),
|
|
360
|
+
clean_goal_list(Goals, CleanGoals),
|
|
361
|
+
( CleanGoals = [Single]
|
|
362
|
+
-> Clean = Single
|
|
363
|
+
; list_to_conjunction(CleanGoals, Clean)
|
|
364
|
+
).
|
|
365
|
+
|
|
366
|
+
conj_list((A, B), List) :-
|
|
367
|
+
!,
|
|
368
|
+
conj_list(A, LA),
|
|
369
|
+
conj_list(B, LB),
|
|
370
|
+
append(LA, LB, List).
|
|
371
|
+
conj_list(Goal, [Goal]).
|
|
372
|
+
|
|
373
|
+
clean_goal_list([], []) :- !.
|
|
374
|
+
clean_goal_list([Goal], [Clean]) :-
|
|
375
|
+
!,
|
|
376
|
+
clean_atomic(Goal, Clean).
|
|
377
|
+
clean_goal_list([First, Second | Rest], [CleanFirst | CleanRest]) :-
|
|
378
|
+
( merge_interpolation(First, Second, CleanFirst)
|
|
379
|
+
-> clean_goal_list(Rest, CleanRest)
|
|
380
|
+
; clean_atomic(First, CleanFirst),
|
|
381
|
+
clean_goal_list([Second | Rest], CleanRest)
|
|
382
|
+
).
|
|
383
|
+
|
|
384
|
+
%% merge_interpolation(+FormatGoal, +TaskGoal, -Merged)
|
|
385
|
+
%% Fold `format(string(D), T, Args), task_named(D, ...)` back into
|
|
386
|
+
%% `task("…{Var}…", ...)`.
|
|
387
|
+
merge_interpolation(FormatGoal, TaskGoal, Merged) :-
|
|
388
|
+
FormatGoal = format(string(D), Template, Args),
|
|
389
|
+
( TaskGoal = task_named(D, Vars, Infos)
|
|
390
|
+
; TaskGoal = prompt_named(D, Vars, Infos)
|
|
391
|
+
),
|
|
392
|
+
task_functor(TaskGoal, Functor),
|
|
393
|
+
interpolate_template(Template, Args, Description),
|
|
394
|
+
clean_task(Functor, Description, Vars, Infos, Merged).
|
|
395
|
+
|
|
396
|
+
task_functor(task_named(_, _, _), task).
|
|
397
|
+
task_functor(prompt_named(_, _, _), prompt).
|
|
398
|
+
|
|
399
|
+
clean_atomic(task_named(D, Vars, Infos), Clean) :-
|
|
400
|
+
!,
|
|
401
|
+
clean_task(task, D, Vars, Infos, Clean).
|
|
402
|
+
clean_atomic(prompt_named(D, Vars, Infos), Clean) :-
|
|
403
|
+
!,
|
|
404
|
+
clean_task(prompt, D, Vars, Infos, Clean).
|
|
405
|
+
clean_atomic((A ; B), (CA ; CB)) :-
|
|
406
|
+
!,
|
|
407
|
+
clean_clause_body(A, CA),
|
|
408
|
+
clean_clause_body(B, CB).
|
|
409
|
+
clean_atomic((A -> B), (CA -> CB)) :-
|
|
410
|
+
!,
|
|
411
|
+
clean_clause_body(A, CA),
|
|
412
|
+
clean_clause_body(B, CB).
|
|
413
|
+
clean_atomic((A -> B ; C), (CA -> CB ; CC)) :-
|
|
414
|
+
!,
|
|
415
|
+
clean_clause_body(A, CA),
|
|
416
|
+
clean_clause_body(B, CB),
|
|
417
|
+
clean_clause_body(C, CC).
|
|
418
|
+
clean_atomic(\+(A), \+ CA) :-
|
|
419
|
+
!,
|
|
420
|
+
clean_clause_body(A, CA).
|
|
421
|
+
clean_atomic(not(A), not(CA)) :-
|
|
422
|
+
!,
|
|
423
|
+
clean_clause_body(A, CA).
|
|
424
|
+
% Wrappers: recurse into their goal arguments so a task/prompt inside is also
|
|
425
|
+
% reversed (the load-time rewrite descends into these via transform_task_calls).
|
|
426
|
+
clean_atomic(with_tools(Tools, Goal), with_tools(Tools, CleanGoal)) :-
|
|
427
|
+
!,
|
|
428
|
+
clean_clause_body(Goal, CleanGoal).
|
|
429
|
+
clean_atomic(without_tools(Tools, Goal), without_tools(Tools, CleanGoal)) :-
|
|
430
|
+
!,
|
|
431
|
+
clean_clause_body(Goal, CleanGoal).
|
|
432
|
+
clean_atomic(with_judgment(Backend, Goal), with_judgment(Backend, CleanGoal)) :-
|
|
433
|
+
!,
|
|
434
|
+
clean_clause_body(Goal, CleanGoal).
|
|
435
|
+
clean_atomic(require_judgment(Backend, Goal), require_judgment(Backend, CleanGoal)) :-
|
|
436
|
+
!,
|
|
437
|
+
clean_clause_body(Goal, CleanGoal).
|
|
438
|
+
clean_atomic(once(Goal), once(CleanGoal)) :-
|
|
439
|
+
!,
|
|
440
|
+
clean_clause_body(Goal, CleanGoal).
|
|
441
|
+
clean_atomic(ignore(Goal), ignore(CleanGoal)) :-
|
|
442
|
+
!,
|
|
443
|
+
clean_clause_body(Goal, CleanGoal).
|
|
444
|
+
clean_atomic(catch(Goal, Catcher, Recovery), catch(CleanGoal, Catcher, CleanRecovery)) :-
|
|
445
|
+
!,
|
|
446
|
+
clean_clause_body(Goal, CleanGoal),
|
|
447
|
+
clean_clause_body(Recovery, CleanRecovery).
|
|
448
|
+
clean_atomic(forall(Generator, Action), forall(CleanGenerator, CleanAction)) :-
|
|
449
|
+
!,
|
|
450
|
+
clean_clause_body(Generator, CleanGenerator),
|
|
451
|
+
clean_clause_body(Action, CleanAction).
|
|
452
|
+
clean_atomic(findall(Template, Goal, List), findall(Template, CleanGoal, List)) :-
|
|
453
|
+
!,
|
|
454
|
+
clean_clause_body(Goal, CleanGoal).
|
|
455
|
+
clean_atomic(Module:Goal, Module:CleanGoal) :-
|
|
456
|
+
!,
|
|
457
|
+
clean_clause_body(Goal, CleanGoal).
|
|
458
|
+
clean_atomic(Goal, Goal).
|
|
459
|
+
|
|
460
|
+
%% clean_task(+Functor, +Description, +Vars, +Infos, -Clean)
|
|
461
|
+
clean_task(Functor, Description, Vars, Infos, Clean) :-
|
|
462
|
+
clean_typed_args(Vars, Infos, CleanVars),
|
|
463
|
+
Clean =.. [Functor, Description | CleanVars].
|
|
464
|
+
|
|
465
|
+
clean_typed_args([], [], []) :- !.
|
|
466
|
+
clean_typed_args([Var | Vars], [Info | Infos], [Wrapped | Wrappeds]) :-
|
|
467
|
+
!,
|
|
468
|
+
typed_arg(Var, Info, Wrapped),
|
|
469
|
+
clean_typed_args(Vars, Infos, Wrappeds).
|
|
470
|
+
clean_typed_args(Vars, _, Vars).
|
|
471
|
+
|
|
472
|
+
%% typed_arg(+Var, +Info, -Wrapped) — reverse typed_var{...} to its wrapper.
|
|
473
|
+
typed_arg(Var, Info, Wrapped) :-
|
|
474
|
+
is_dict(Info),
|
|
475
|
+
get_dict(type, Info, Type),
|
|
476
|
+
!,
|
|
477
|
+
typed_arg_type(Type, Var, Info, Wrapped).
|
|
478
|
+
typed_arg(Var, _, Var).
|
|
479
|
+
|
|
480
|
+
typed_arg_type(string, Var, _, Var) :- !.
|
|
481
|
+
typed_arg_type(integer, Var, _, integer(Var)) :- !.
|
|
482
|
+
typed_arg_type(number, Var, _, number(Var)) :- !.
|
|
483
|
+
typed_arg_type(boolean, Var, _, boolean(Var)) :- !.
|
|
484
|
+
typed_arg_type(object, Var, _, object(Var)) :- !.
|
|
485
|
+
typed_arg_type(array, Var, Info, list(Wrapped)) :-
|
|
486
|
+
!,
|
|
487
|
+
( get_dict(itemType, Info, ItemType)
|
|
488
|
+
-> true
|
|
489
|
+
; ItemType = string
|
|
490
|
+
),
|
|
491
|
+
Wrapped =.. [ItemType, Var].
|
|
492
|
+
typed_arg_type(_, Var, _, Var).
|
|
493
|
+
|
|
494
|
+
%% interpolate_template(+Template, +Args, -Description)
|
|
495
|
+
%% Reverse of the {Var} -> ~w hoist. Succeeds only when every placeholder is a
|
|
496
|
+
%% plain variable, so the caller can fall back to keeping the format/3 goal.
|
|
497
|
+
interpolate_template(Template, Args, Description) :-
|
|
498
|
+
string(Template),
|
|
499
|
+
string_codes(Template, Codes),
|
|
500
|
+
interpolate_codes(Codes, Args, OutCodes),
|
|
501
|
+
string_codes(Description, OutCodes).
|
|
502
|
+
|
|
503
|
+
interpolate_codes([], [], []) :- !.
|
|
504
|
+
%% A {Var} placeholder consumes one argument; require Args to be exhausted
|
|
505
|
+
%% exactly (the []/[] base case above fails if either side has leftovers).
|
|
506
|
+
interpolate_codes([0'~, 0'w | Rest], [Arg | Args], Out) :-
|
|
507
|
+
!,
|
|
508
|
+
named_var(Arg),
|
|
509
|
+
term_string(Arg, Name, [numbervars(true)]),
|
|
510
|
+
string_codes(Name, NameCodes),
|
|
511
|
+
interpolate_codes(Rest, Args, Tail),
|
|
512
|
+
append([0'{|NameCodes], [0'}|Tail], Out).
|
|
513
|
+
interpolate_codes([0'~, 0'~ | Rest], Args, [0'~ | Out]) :-
|
|
514
|
+
!,
|
|
515
|
+
interpolate_codes(Rest, Args, Out).
|
|
516
|
+
interpolate_codes([Code | Rest], Args, [Code | Out]) :-
|
|
517
|
+
interpolate_codes(Rest, Args, Out).
|
|
518
|
+
|
|
519
|
+
%% An interpolation argument is a variable or a `$VAR(N)` term (after
|
|
520
|
+
%% numbervars), both of which print as a stable name (A, B, ...).
|
|
521
|
+
named_var(Arg) :- var(Arg), !.
|
|
522
|
+
named_var('$VAR'(_)).
|
|
523
|
+
|
|
261
524
|
%% var_to_name(+Bindings, +Var, -Name)
|
|
262
525
|
%% Look up a variable's name from the bindings list
|
|
263
526
|
var_to_name(Bindings, Var, Name) :-
|
|
@@ -267,7 +530,15 @@ var_to_name(Bindings, Var, Name) :-
|
|
|
267
530
|
).
|
|
268
531
|
|
|
269
532
|
%% process_clause(+Term, +SessionId)
|
|
270
|
-
%% Process a single clause and add to session
|
|
533
|
+
%% Process a single clause and add to session.
|
|
534
|
+
%% Reject reserved-name definitions (third place of the reserved guard, so
|
|
535
|
+
%% consulted/generated code cannot shadow the REPL control surface).
|
|
536
|
+
process_clause(Term, _SessionId) :-
|
|
537
|
+
repl_reserved_clause(Term),
|
|
538
|
+
!,
|
|
539
|
+
throw(error(permission_error(modify, reserved_name, Term),
|
|
540
|
+
context(process_clause/2, 'reserved predicate'))).
|
|
541
|
+
|
|
271
542
|
process_clause((:- Directive), SessionId) :-
|
|
272
543
|
!,
|
|
273
544
|
process_directive(Directive, SessionId).
|
|
@@ -279,8 +550,9 @@ process_clause((tool(ToolHead, Description) :- Body), SessionId) :-
|
|
|
279
550
|
extract_tool_schema(ToolHead, Description, ToolName, Schema),
|
|
280
551
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
281
552
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
282
|
-
% Store the source code for the tool description
|
|
283
|
-
|
|
553
|
+
% Store the source code for the tool description. Reverses the load-time
|
|
554
|
+
% task/prompt rewrites and normalizes variables for deterministic export.
|
|
555
|
+
format_tool_source(ToolHead, Description, Body, SourceCode),
|
|
284
556
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
285
557
|
% Assert the tool implementation (use just ToolHead for execution)
|
|
286
558
|
assertz(SessionId:(tool(ToolHead) :- ExpandedBody)).
|
|
@@ -292,8 +564,8 @@ process_clause((tool(ToolHead) :- Body), SessionId) :-
|
|
|
292
564
|
extract_tool_schema(ToolHead, none, ToolName, Schema),
|
|
293
565
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
294
566
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
295
|
-
% Store the source code for the tool description
|
|
296
|
-
|
|
567
|
+
% Store the source code for the tool description (reverses task rewrites).
|
|
568
|
+
format_tool_source(ToolHead, none, Body, SourceCode),
|
|
297
569
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
298
570
|
% Assert the tool implementation
|
|
299
571
|
assertz(SessionId:(tool(ToolHead) :- ExpandedBody)).
|
|
@@ -307,6 +579,15 @@ process_clause((Head :- Body), SessionId) :-
|
|
|
307
579
|
),
|
|
308
580
|
assertz(SessionId:(Head :- ExpandedBody)).
|
|
309
581
|
|
|
582
|
+
%% repl_delete/1 is a load-time directive that removes a predicate. It must
|
|
583
|
+
%% be handled before the generic fact clause so it is never asserted itself.
|
|
584
|
+
process_clause(repl_delete(Name/Arity), SessionId) :-
|
|
585
|
+
!,
|
|
586
|
+
( atom(Name), integer(Arity)
|
|
587
|
+
-> catch(abolish(SessionId:Name/Arity), _, true)
|
|
588
|
+
; true
|
|
589
|
+
).
|
|
590
|
+
|
|
310
591
|
process_clause(Fact, SessionId) :-
|
|
311
592
|
% Simple fact
|
|
312
593
|
assertz(SessionId:Fact).
|
|
@@ -393,100 +674,1742 @@ extract_tool_schema(ToolHead, Description, ToolName, Schema) :-
|
|
|
393
674
|
outputs: Outputs
|
|
394
675
|
}.
|
|
395
676
|
|
|
396
|
-
%% extract_params(+Args, +Index, +Arity, -Inputs, -Outputs)
|
|
397
|
-
extract_params([], _, _, [], []) :- !.
|
|
398
|
-
extract_params([_Arg|Rest], Index, Arity, Inputs, Outputs) :-
|
|
399
|
-
format(atom(Name), 'arg~w', [Index]),
|
|
400
|
-
Type = string,
|
|
401
|
-
NextIndex is Index + 1,
|
|
402
|
-
extract_params(Rest, NextIndex, Arity, RestInputs, RestOutputs),
|
|
403
|
-
Param = param{name: Name, type: Type},
|
|
404
|
-
( Index == Arity
|
|
405
|
-
-> Inputs = RestInputs, Outputs = [Param|RestOutputs]
|
|
406
|
-
; Inputs = [Param|RestInputs], Outputs = RestOutputs
|
|
677
|
+
%% extract_params(+Args, +Index, +Arity, -Inputs, -Outputs)
|
|
678
|
+
extract_params([], _, _, [], []) :- !.
|
|
679
|
+
extract_params([_Arg|Rest], Index, Arity, Inputs, Outputs) :-
|
|
680
|
+
format(atom(Name), 'arg~w', [Index]),
|
|
681
|
+
Type = string,
|
|
682
|
+
NextIndex is Index + 1,
|
|
683
|
+
extract_params(Rest, NextIndex, Arity, RestInputs, RestOutputs),
|
|
684
|
+
Param = param{name: Name, type: Type},
|
|
685
|
+
( Index == Arity
|
|
686
|
+
-> Inputs = RestInputs, Outputs = [Param|RestOutputs]
|
|
687
|
+
; Inputs = [Param|RestInputs], Outputs = RestOutputs
|
|
688
|
+
).
|
|
689
|
+
|
|
690
|
+
module_reference_string(Module, ModuleStr) :-
|
|
691
|
+
atom(Module),
|
|
692
|
+
!,
|
|
693
|
+
atom_string(Module, ModuleStr).
|
|
694
|
+
module_reference_string(Module, Module) :-
|
|
695
|
+
string(Module).
|
|
696
|
+
|
|
697
|
+
workspace_module_reference(Module) :-
|
|
698
|
+
module_reference_string(Module, ModuleStr),
|
|
699
|
+
( sub_string(ModuleStr, _, _, _, "/")
|
|
700
|
+
; sub_string(ModuleStr, _, _, 0, ".dml")
|
|
701
|
+
; sub_string(ModuleStr, _, _, 0, ".pl")
|
|
702
|
+
; sub_string(ModuleStr, 0, 1, _, ".")
|
|
703
|
+
).
|
|
704
|
+
|
|
705
|
+
process_use_module_directive(Module, SessionId) :-
|
|
706
|
+
( workspace_module_reference(Module)
|
|
707
|
+
-> process_directive(consult(Module), SessionId)
|
|
708
|
+
; SessionId:use_module(Module),
|
|
709
|
+
import_library_into_mi(Module)
|
|
710
|
+
).
|
|
711
|
+
|
|
712
|
+
process_use_module_directive(Module, Exports, SessionId) :-
|
|
713
|
+
( workspace_module_reference(Module)
|
|
714
|
+
-> process_directive(consult(Module), SessionId)
|
|
715
|
+
; SessionId:use_module(Module, Exports),
|
|
716
|
+
import_library_into_mi(Module)
|
|
717
|
+
).
|
|
718
|
+
|
|
719
|
+
%% System libraries are also imported into the meta-interpreter module itself,
|
|
720
|
+
%% so meta-calls (`call_with_inference_limit/3`, `once/1`, `catch/3`, ...) can
|
|
721
|
+
%% resolve them. Without this, `call_with_inference_limit(label(Xs), ...)` fails
|
|
722
|
+
%% with `existence_error(deepclause_mi:label/1)` because clpfd was imported only
|
|
723
|
+
%% into the session module.
|
|
724
|
+
import_library_into_mi(library(Name)) :-
|
|
725
|
+
!,
|
|
726
|
+
catch(deepclause_mi:use_module(library(Name)), _, true).
|
|
727
|
+
import_library_into_mi(_).
|
|
728
|
+
|
|
729
|
+
%% process_directive(+Directive, +SessionId)
|
|
730
|
+
process_directive(param(Key, Desc), SessionId) :-
|
|
731
|
+
!,
|
|
732
|
+
assertz(SessionId:param_decl(Key, Desc)).
|
|
733
|
+
process_directive(param(Key, Desc, Default), SessionId) :-
|
|
734
|
+
!,
|
|
735
|
+
assertz(SessionId:param_decl(Key, Desc, Default)).
|
|
736
|
+
|
|
737
|
+
%% Handle consult directive: :- consult(File)
|
|
738
|
+
process_directive(consult(File), SessionId) :-
|
|
739
|
+
!,
|
|
740
|
+
% Set session ID for get_session_id to find, then call our custom consult
|
|
741
|
+
nb_setval(current_session_id, SessionId),
|
|
742
|
+
resolve_workspace_path(File, FullPath),
|
|
743
|
+
readutil:read_file_to_string(FullPath, Code, []),
|
|
744
|
+
consult_string(Code, SessionId).
|
|
745
|
+
%% Handle use_module directive
|
|
746
|
+
process_directive(use_module(Module), SessionId) :-
|
|
747
|
+
!,
|
|
748
|
+
( workspace_module_reference(Module)
|
|
749
|
+
-> true
|
|
750
|
+
; record_directive(SessionId, use_module(Module))
|
|
751
|
+
),
|
|
752
|
+
process_use_module_directive(Module, SessionId).
|
|
753
|
+
process_directive(use_module(Module, Exports), SessionId) :-
|
|
754
|
+
!,
|
|
755
|
+
( workspace_module_reference(Module)
|
|
756
|
+
-> true
|
|
757
|
+
; record_directive(SessionId, use_module(Module, Exports))
|
|
758
|
+
),
|
|
759
|
+
process_use_module_directive(Module, Exports, SessionId).
|
|
760
|
+
%% Handle list-style consult: :- [File] or :- [File1, File2, ...]
|
|
761
|
+
process_directive([File|Files], SessionId) :-
|
|
762
|
+
!,
|
|
763
|
+
nb_setval(current_session_id, SessionId),
|
|
764
|
+
process_directive(consult(File), SessionId),
|
|
765
|
+
(Files == [] -> true ; process_directive(Files, SessionId)).
|
|
766
|
+
process_directive(_, _).
|
|
767
|
+
|
|
768
|
+
%% ============================================================
|
|
769
|
+
%% Engine Management
|
|
770
|
+
%% ============================================================
|
|
771
|
+
|
|
772
|
+
%% create_engine(+SessionId, +MemoryId, +Args, +Params, +InitialMemory, -Engine)
|
|
773
|
+
%% Args is a list of positional arguments for agent_main
|
|
774
|
+
%% Params is a dict of named parameters (already asserted as param/2 facts)
|
|
775
|
+
%%
|
|
776
|
+
%% The engine is registered as the session's default `query` frame so that
|
|
777
|
+
%% step_engine/4 keeps working as a thin wrapper over step_frame/4.
|
|
778
|
+
create_engine(SessionId, _MemoryId, Args, Params, InitialMemory, Engine) :-
|
|
779
|
+
assertz(session_params(SessionId, Params)),
|
|
780
|
+
determine_agent_goal(SessionId, Args, Goal),
|
|
781
|
+
% Check if tracing is enabled and store in session state
|
|
782
|
+
(get_dict(trace, Params, true) -> TraceEnabled = true ; TraceEnabled = false),
|
|
783
|
+
assertz(session_trace_enabled(SessionId, TraceEnabled)),
|
|
784
|
+
assertz(session_trace_log(SessionId, [])),
|
|
785
|
+
% Check for gas limit in params
|
|
786
|
+
(get_dict(gas_limit, Params, GasLimit) -> true ; GasLimit = -1),
|
|
787
|
+
% Create initial state with provided memory, params, context stack, trace depth and gas
|
|
788
|
+
InitialState = state{memory: InitialMemory, params: Params, context_stack: [], depth: 0, gas_remaining: GasLimit},
|
|
789
|
+
create_frame(SessionId, query, 0, Goal, InitialState, FrameId),
|
|
790
|
+
retractall(session_default_frame(SessionId, _)),
|
|
791
|
+
assertz(session_default_frame(SessionId, FrameId)),
|
|
792
|
+
session_frame_engine(FrameId, Engine),
|
|
793
|
+
retractall(session_engine(SessionId, _)),
|
|
794
|
+
assertz(session_engine(SessionId, Engine)).
|
|
795
|
+
|
|
796
|
+
%% ============================================================
|
|
797
|
+
%% Frames (re-entrant execution)
|
|
798
|
+
%% ============================================================
|
|
799
|
+
%%
|
|
800
|
+
%% Historically a session owned exactly one engine (session_engine/2).
|
|
801
|
+
%% Prompt mode requires nested engines: a meta frame that calls `execute`
|
|
802
|
+
%% which runs a child frame, which may itself call task() and spawn a
|
|
803
|
+
%% further frame. A frame is therefore (FrameId, SessionId, Kind, Depth)
|
|
804
|
+
%% with its engine stored in session_frame_engine/2.
|
|
805
|
+
%%
|
|
806
|
+
%% The legacy step_engine/4 resolves the session's default query frame and
|
|
807
|
+
%% delegates to step_frame/4, so single-engine callers are unaffected.
|
|
808
|
+
|
|
809
|
+
default_frame(SessionId, FrameId) :-
|
|
810
|
+
session_default_frame(SessionId, FrameId).
|
|
811
|
+
|
|
812
|
+
%% frame_info(+FrameId, -SessionId, -Kind, -Depth)
|
|
813
|
+
frame_info(FrameId, SessionId, Kind, Depth) :-
|
|
814
|
+
session_frame(FrameId, SessionId, Kind, Depth).
|
|
815
|
+
|
|
816
|
+
%% create_frame_raw(+SessionId, +Kind, +Depth, +EngineGoal, -FrameId)
|
|
817
|
+
%% Registers a frame whose engine runs EngineGoal verbatim. Most callers want
|
|
818
|
+
%% create_frame/6, which wraps the goal in the mi/3 meta-interpreter; goal mode
|
|
819
|
+
%% uses this to run repl_solve/4 directly (mi/3 cuts after the first solution).
|
|
820
|
+
create_frame_raw(SessionId, Kind, Depth, EngineGoal, FrameId) :-
|
|
821
|
+
gensym(frame_, FrameId),
|
|
822
|
+
engine_create(_, EngineGoal, Engine),
|
|
823
|
+
assertz(session_frame(FrameId, SessionId, Kind, Depth)),
|
|
824
|
+
assertz(session_frame_engine(FrameId, Engine)).
|
|
825
|
+
|
|
826
|
+
%% create_frame(+SessionId, +Kind, +Depth, +Goal, +InitialState, -FrameId)
|
|
827
|
+
%% Kind = query | meta | nested | object
|
|
828
|
+
%% Creates an engine running deepclause_mi:mi(Goal, InitialState, SessionId)
|
|
829
|
+
%% and registers it as a frame. The caller is responsible for depth-first
|
|
830
|
+
%% stepping: a parent frame is suspended while a child frame runs, so the
|
|
831
|
+
%% per-session result/signal slots are never concurrently pending.
|
|
832
|
+
create_frame(SessionId, Kind, Depth, Goal, InitialState, FrameId) :-
|
|
833
|
+
create_frame_raw(SessionId, Kind, Depth,
|
|
834
|
+
deepclause_mi:mi(Goal, InitialState, SessionId),
|
|
835
|
+
FrameId).
|
|
836
|
+
|
|
837
|
+
%% step_frame(+FrameId, -Status, -Content, -Payload)
|
|
838
|
+
step_frame(FrameId, Status, Content, Payload) :-
|
|
839
|
+
( session_frame(FrameId, SessionId, _Kind, _Depth),
|
|
840
|
+
session_frame_engine(FrameId, Engine)
|
|
841
|
+
-> step_engine_handle(SessionId, Engine, Status, Content, Payload)
|
|
842
|
+
; Status = error,
|
|
843
|
+
Content = 'No engine found for frame',
|
|
844
|
+
Payload = none
|
|
845
|
+
).
|
|
846
|
+
|
|
847
|
+
%% step_engine_handle(+SessionId, +Engine, -Status, -Content, -Payload)
|
|
848
|
+
%% Shared stepping logic for both step_frame/4 and the legacy step_engine/4.
|
|
849
|
+
step_engine_handle(SessionId, Engine, Status, Content, Payload) :-
|
|
850
|
+
catch(
|
|
851
|
+
( engine_next(Engine, Result)
|
|
852
|
+
-> process_engine_result(Result, Status, Content, Payload)
|
|
853
|
+
; % Engine finished - return trace if enabled
|
|
854
|
+
Status = finished,
|
|
855
|
+
Content = '',
|
|
856
|
+
( session_trace_enabled(SessionId, true),
|
|
857
|
+
session_trace_log(SessionId, TraceLog)
|
|
858
|
+
-> Payload = payload{trace: TraceLog}
|
|
859
|
+
; Payload = none
|
|
860
|
+
)
|
|
861
|
+
),
|
|
862
|
+
Error,
|
|
863
|
+
( format(atom(ErrMsg), 'Engine error: ~w', [Error]),
|
|
864
|
+
Status = error,
|
|
865
|
+
Content = ErrMsg,
|
|
866
|
+
( session_trace_enabled(SessionId, true),
|
|
867
|
+
session_trace_log(SessionId, TraceLog)
|
|
868
|
+
-> Payload = payload{trace: TraceLog}
|
|
869
|
+
; Payload = none
|
|
870
|
+
)
|
|
871
|
+
)
|
|
872
|
+
).
|
|
873
|
+
|
|
874
|
+
%% end_frame(+FrameId)
|
|
875
|
+
%% Destroys the frame's engine and unregisters the frame. Also clears the
|
|
876
|
+
%% session's default-frame pointer if this frame was the default.
|
|
877
|
+
end_frame(FrameId) :-
|
|
878
|
+
( session_frame_engine(FrameId, Engine)
|
|
879
|
+
-> catch(engine_destroy(Engine), _, true)
|
|
880
|
+
; true
|
|
881
|
+
),
|
|
882
|
+
( session_frame(FrameId, SessionId, _, _)
|
|
883
|
+
-> retractall(session_default_frame(SessionId, FrameId))
|
|
884
|
+
; true
|
|
885
|
+
),
|
|
886
|
+
retractall(session_frame_engine(FrameId, _)),
|
|
887
|
+
retractall(session_frame(FrameId, _, _, _)).
|
|
888
|
+
|
|
889
|
+
%% ============================================================
|
|
890
|
+
%% REPL Session and Goal Mode (M1)
|
|
891
|
+
%% ============================================================
|
|
892
|
+
%%
|
|
893
|
+
%% A REPL session owns a persistent living module. Unlike create_engine/6,
|
|
894
|
+
%% which is tied to a single run and destroyed by destroy_engine/1, a session
|
|
895
|
+
%% lives across inputs; reset/clear_session empties it without tearing down the
|
|
896
|
+
%% process-level SWI instance.
|
|
897
|
+
|
|
898
|
+
%% create_session(+SessionId, +Params, +HarnessMemory, +MetaMemory)
|
|
899
|
+
create_session(SessionId, Params, HarnessMemory, MetaMemory) :-
|
|
900
|
+
retractall(session_params(SessionId, _)),
|
|
901
|
+
assertz(session_params(SessionId, Params)),
|
|
902
|
+
( get_dict(trace, Params, true)
|
|
903
|
+
-> TraceEnabled = true
|
|
904
|
+
; TraceEnabled = false
|
|
905
|
+
),
|
|
906
|
+
retractall(session_trace_enabled(SessionId, _)),
|
|
907
|
+
assertz(session_trace_enabled(SessionId, TraceEnabled)),
|
|
908
|
+
retractall(session_trace_log(SessionId, _)),
|
|
909
|
+
assertz(session_trace_log(SessionId, [])),
|
|
910
|
+
retractall(session_repl_memory(SessionId, _)),
|
|
911
|
+
assertz(session_repl_memory(SessionId, HarnessMemory)),
|
|
912
|
+
retractall(session_meta_memory(SessionId, _)),
|
|
913
|
+
assertz(session_meta_memory(SessionId, MetaMemory)),
|
|
914
|
+
retractall(session_repl_mode(SessionId, _)),
|
|
915
|
+
assertz(session_repl_mode(SessionId, true)),
|
|
916
|
+
retractall(session_frozen(SessionId, _)),
|
|
917
|
+
assertz(session_frozen(SessionId, false)),
|
|
918
|
+
retractall(session_locked(SessionId, _)),
|
|
919
|
+
assertz(session_locked(SessionId, false)).
|
|
920
|
+
|
|
921
|
+
%% repl_abolish_user_predicates(+SessionId)
|
|
922
|
+
%% Removes only the harness's own predicates; imported builtins are untouched.
|
|
923
|
+
repl_abolish_user_predicates(SessionId) :-
|
|
924
|
+
( current_module(SessionId)
|
|
925
|
+
-> findall(Name/Arity,
|
|
926
|
+
( current_predicate(SessionId:Name/Arity),
|
|
927
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
928
|
+
),
|
|
929
|
+
UserPreds0),
|
|
930
|
+
sort(UserPreds0, UserPreds),
|
|
931
|
+
forall(member(Name/Arity, UserPreds),
|
|
932
|
+
catch(abolish(SessionId:Name/Arity), _, true))
|
|
933
|
+
; true
|
|
934
|
+
).
|
|
935
|
+
|
|
936
|
+
%% clear_session(+SessionId)
|
|
937
|
+
%% Empties the living module, ends all frames, and resets memories. Session
|
|
938
|
+
%% params (workspace/gas limit) are preserved so the REPL stays configured.
|
|
939
|
+
clear_session(SessionId) :-
|
|
940
|
+
( findall(FrameId, session_frame(FrameId, SessionId, _, _), FrameIds)
|
|
941
|
+
-> forall(member(FrameId, FrameIds), end_frame(FrameId))
|
|
942
|
+
; true
|
|
943
|
+
),
|
|
944
|
+
( session_engine(SessionId, Engine)
|
|
945
|
+
-> catch(engine_destroy(Engine), _, true)
|
|
946
|
+
; true
|
|
947
|
+
),
|
|
948
|
+
retractall(session_engine(SessionId, _)),
|
|
949
|
+
retractall(session_default_frame(SessionId, _)),
|
|
950
|
+
retractall(session_repl_memory(SessionId, _)),
|
|
951
|
+
assertz(session_repl_memory(SessionId, [])),
|
|
952
|
+
retractall(session_meta_memory(SessionId, _)),
|
|
953
|
+
assertz(session_meta_memory(SessionId, [])),
|
|
954
|
+
retractall(repl_snapshot(SessionId, _)),
|
|
955
|
+
retractall(session_user_tools(SessionId, _)),
|
|
956
|
+
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
957
|
+
repl_abolish_user_predicates(SessionId).
|
|
958
|
+
|
|
959
|
+
%% destroy_session(+SessionId)
|
|
960
|
+
%% Tears a REPL session down completely: clears the module/frames and removes
|
|
961
|
+
%% the session's dynamic bookkeeping so the process can host another session.
|
|
962
|
+
destroy_session(SessionId) :-
|
|
963
|
+
clear_session(SessionId),
|
|
964
|
+
retractall(session_repl_memory(SessionId, _)),
|
|
965
|
+
retractall(session_meta_memory(SessionId, _)),
|
|
966
|
+
retractall(session_repl_mode(SessionId, _)),
|
|
967
|
+
retractall(session_frozen(SessionId, _)),
|
|
968
|
+
retractall(session_locked(SessionId, _)),
|
|
969
|
+
retractall(session_directive(SessionId, _)),
|
|
970
|
+
retractall(session_meta_catalog(SessionId, _)),
|
|
971
|
+
retractall(session_dml_reference(SessionId, _)),
|
|
972
|
+
retractall(session_authoring_examples(SessionId, _)),
|
|
973
|
+
retractall(session_meta_template(SessionId, _)),
|
|
974
|
+
retractall(session_harness_examples(SessionId, _)),
|
|
975
|
+
retractall(session_prolog_snippets(SessionId, _)),
|
|
976
|
+
retractall(session_last_commit(SessionId, _)),
|
|
977
|
+
retractall(repl_snapshot(SessionId, _)),
|
|
978
|
+
retractall(session_params(SessionId, _)),
|
|
979
|
+
retractall(session_trace_enabled(SessionId, _)),
|
|
980
|
+
retractall(session_trace_log(SessionId, _)).
|
|
981
|
+
|
|
982
|
+
%% load_code(+SessionId, +Code, -Error)
|
|
983
|
+
%% Parses DML source into the session module, running the normal source
|
|
984
|
+
%% preprocessing (task/prompt rewrite, interpolation, expand_term).
|
|
985
|
+
load_code(SessionId, Code, Error) :-
|
|
986
|
+
catch(
|
|
987
|
+
( guard_not_frozen(SessionId),
|
|
988
|
+
parse_dml_string(Code, SessionId),
|
|
989
|
+
Error = none
|
|
990
|
+
),
|
|
991
|
+
LoadError,
|
|
992
|
+
format(atom(Error), '~w', [LoadError])
|
|
993
|
+
).
|
|
994
|
+
|
|
995
|
+
%% repl_initial_state(+SessionId, +FrameKind, -InitialState)
|
|
996
|
+
%% Builds the state{} threaded through a REPL frame. M1 seeds harness memory
|
|
997
|
+
%% from session_repl_memory/2; M4 adds the meta/scratch slots.
|
|
998
|
+
repl_initial_state(SessionId, FrameKind, InitialState) :-
|
|
999
|
+
session_params(SessionId, Params),
|
|
1000
|
+
( get_dict(gas_limit, Params, GasLimit)
|
|
1001
|
+
-> true
|
|
1002
|
+
; GasLimit = -1
|
|
1003
|
+
),
|
|
1004
|
+
% Two-level memory: meta (code-writing) and harness (runtime) are kept
|
|
1005
|
+
% apart. The state carries the slot so write-back goes to the right one.
|
|
1006
|
+
( FrameKind == meta
|
|
1007
|
+
-> MemorySlot = meta,
|
|
1008
|
+
( session_meta_memory(SessionId, Memory)
|
|
1009
|
+
-> true
|
|
1010
|
+
; Memory = []
|
|
1011
|
+
)
|
|
1012
|
+
; MemorySlot = harness,
|
|
1013
|
+
( session_repl_memory(SessionId, Memory)
|
|
1014
|
+
-> true
|
|
1015
|
+
; Memory = []
|
|
1016
|
+
)
|
|
1017
|
+
),
|
|
1018
|
+
InitialState = state{
|
|
1019
|
+
memory: Memory,
|
|
1020
|
+
params: Params,
|
|
1021
|
+
context_stack: [],
|
|
1022
|
+
depth: 0,
|
|
1023
|
+
gas_remaining: GasLimit,
|
|
1024
|
+
memory_slot: MemorySlot,
|
|
1025
|
+
frame_kind: FrameKind
|
|
1026
|
+
}.
|
|
1027
|
+
|
|
1028
|
+
%% query_start(+SessionId, +Text, +FrameKind, +InitialState, -Error)
|
|
1029
|
+
%% Parses and preprocesses a goal string, then creates a frame running
|
|
1030
|
+
%% repl_solve/4. The frame becomes the session's default frame so that
|
|
1031
|
+
%% step_engine/4 and the runner can drive it.
|
|
1032
|
+
query_start(SessionId, Text, FrameKind, InitialState, Error) :-
|
|
1033
|
+
catch(
|
|
1034
|
+
( read_repl_goal(SessionId, Text, Goal, Names),
|
|
1035
|
+
create_frame_raw(SessionId, FrameKind, 0,
|
|
1036
|
+
deepclause_mi:repl_solve(Goal, Names, SessionId, InitialState),
|
|
1037
|
+
FrameId),
|
|
1038
|
+
retractall(session_default_frame(SessionId, _)),
|
|
1039
|
+
assertz(session_default_frame(SessionId, FrameId)),
|
|
1040
|
+
session_frame_engine(FrameId, Engine),
|
|
1041
|
+
retractall(session_engine(SessionId, _)),
|
|
1042
|
+
assertz(session_engine(SessionId, Engine)),
|
|
1043
|
+
Error = none
|
|
1044
|
+
),
|
|
1045
|
+
QueryError,
|
|
1046
|
+
format(atom(Error), '~w', [QueryError])
|
|
1047
|
+
).
|
|
1048
|
+
|
|
1049
|
+
%% ensure_full_stop(+Text, -TextWithStop)
|
|
1050
|
+
%% Appends a terminating full stop + newline when the goal text lacks one, so
|
|
1051
|
+
%% both `goal: p(X).` and `-g "p(X)"` read cleanly.
|
|
1052
|
+
ensure_full_stop(Text, Text1) :-
|
|
1053
|
+
atom_string(TextAtom, Text),
|
|
1054
|
+
( atom_ends_with_stop(TextAtom)
|
|
1055
|
+
-> atom_concat(TextAtom, '\n', Text1)
|
|
1056
|
+
; atom_concat(TextAtom, '.\n', Text1)
|
|
1057
|
+
).
|
|
1058
|
+
|
|
1059
|
+
%% atom_ends_with_stop(+Atom)
|
|
1060
|
+
%% True when the last non-whitespace character is a full stop. A naive
|
|
1061
|
+
%% "ends with ." test would append an extra full stop to any block that ends
|
|
1062
|
+
%% with a newline, and read_term/3 then fails on the lone '.' (end_of_clause).
|
|
1063
|
+
atom_ends_with_stop(TextAtom) :-
|
|
1064
|
+
atom_string(TextAtom, TextStr),
|
|
1065
|
+
string_length(TextStr, Len0),
|
|
1066
|
+
trim_trailing_ws(TextStr, Len0, Len),
|
|
1067
|
+
Len > 0,
|
|
1068
|
+
Pos is Len - 1,
|
|
1069
|
+
sub_string(TextStr, Pos, 1, _, ".").
|
|
1070
|
+
|
|
1071
|
+
trim_trailing_ws(Str, Len0, Len) :-
|
|
1072
|
+
( Len0 > 0,
|
|
1073
|
+
Pos is Len0 - 1,
|
|
1074
|
+
sub_string(Str, Pos, 1, _, Ch),
|
|
1075
|
+
( Ch == " " ; Ch == "\t" ; Ch == "\n" ; Ch == "\r" )
|
|
1076
|
+
-> Len1 is Len0 - 1,
|
|
1077
|
+
trim_trailing_ws(Str, Len1, Len)
|
|
1078
|
+
; Len = Len0
|
|
1079
|
+
).
|
|
1080
|
+
|
|
1081
|
+
%% read_repl_goal(+SessionId, +Text, -Goal, -Names)
|
|
1082
|
+
%% Names is the read_term variable_names list (Name=Var), used to render
|
|
1083
|
+
%% bindings for solution events.
|
|
1084
|
+
read_repl_goal(SessionId, Text, Goal, Names) :-
|
|
1085
|
+
% read_term/3 requires a full stop; REPL callers may omit it (`-g "p(X)"`).
|
|
1086
|
+
ensure_full_stop(Text, Text1),
|
|
1087
|
+
open_string(Text1, Stream),
|
|
1088
|
+
read_term(Stream, RawGoal, [module(SessionId), variable_names(Names0)]),
|
|
1089
|
+
close(Stream),
|
|
1090
|
+
( RawGoal == end_of_file
|
|
1091
|
+
-> throw(error(syntax_error('empty goal'), _))
|
|
1092
|
+
; true
|
|
1093
|
+
),
|
|
1094
|
+
% Apply only the goal-relevant source transforms. Do NOT run
|
|
1095
|
+
% preprocess_loaded_term/3 here: its file-load term expansion flattens a
|
|
1096
|
+
% list goal such as ["file.dml"] into its elements.
|
|
1097
|
+
transform_task_calls(RawGoal, Names0, Transformed),
|
|
1098
|
+
% Use the body expander (not expand_term_interp/3) so a `{Var}` string is
|
|
1099
|
+
% hoisted into a goal *before* the goal that uses it. expand_term_interp
|
|
1100
|
+
% embeds the interpolation term in the argument position instead, which is
|
|
1101
|
+
% wrong for a raw goal such as verify(X, "Is {X} > 0?", V).
|
|
1102
|
+
expand_body_interp(Transformed, Names0, Goal),
|
|
1103
|
+
Names = Names0.
|
|
1104
|
+
|
|
1105
|
+
%% repl_solve(+Goal, +Names, +SessionId, +State)
|
|
1106
|
+
%% Enumerating goal-mode solver. Yields one solution(Bindings, Deterministic)
|
|
1107
|
+
%% per answer and fails so engine_next/2 backtracks for the next solution.
|
|
1108
|
+
%% answer/1 commits (throws '$answer_commit') and stops enumeration.
|
|
1109
|
+
%% mi/3 is deliberately not used: it cuts after the first solution.
|
|
1110
|
+
repl_solve(Goal, Names, SessionId, State) :-
|
|
1111
|
+
nb_setval(current_session_id, SessionId),
|
|
1112
|
+
catch(
|
|
1113
|
+
repl_solve_loop(Goal, Names, SessionId, State),
|
|
1114
|
+
'$answer_commit',
|
|
1115
|
+
true
|
|
1116
|
+
).
|
|
1117
|
+
|
|
1118
|
+
repl_solve_loop(Goal, Names, SessionId, State) :-
|
|
1119
|
+
mi_call(Goal, State, StateOut),
|
|
1120
|
+
collect_bindings(Names, Bindings),
|
|
1121
|
+
store_memory_for_state(SessionId, StateOut),
|
|
1122
|
+
engine_yield(solution(Bindings, false)),
|
|
1123
|
+
fail.
|
|
1124
|
+
|
|
1125
|
+
%% collect_bindings(+Names, -Bindings)
|
|
1126
|
+
%% Bindings is a list of Name-RenderedText. Unbound variables render as '_'.
|
|
1127
|
+
collect_bindings(Names, Bindings) :-
|
|
1128
|
+
findall(Name-Text,
|
|
1129
|
+
( member(Name=Var, Names),
|
|
1130
|
+
render_binding(Var, Text)
|
|
1131
|
+
),
|
|
1132
|
+
Bindings).
|
|
1133
|
+
|
|
1134
|
+
render_binding(Var, '_') :-
|
|
1135
|
+
var(Var),
|
|
1136
|
+
!.
|
|
1137
|
+
render_binding(Value, Text) :-
|
|
1138
|
+
with_output_to(string(Text), write_term(Value, [quoted(true), max_depth(20)])).
|
|
1139
|
+
|
|
1140
|
+
%% store_memory_for_state(+SessionId, +State)
|
|
1141
|
+
%% Writes the threaded memory back to the slot named by the state.
|
|
1142
|
+
store_memory_for_state(SessionId, State) :-
|
|
1143
|
+
( is_dict(State),
|
|
1144
|
+
get_dict(memory, State, Memory)
|
|
1145
|
+
-> ( get_dict(memory_slot, State, meta)
|
|
1146
|
+
-> retractall(session_meta_memory(SessionId, _)),
|
|
1147
|
+
assertz(session_meta_memory(SessionId, Memory))
|
|
1148
|
+
; retractall(session_repl_memory(SessionId, _)),
|
|
1149
|
+
assertz(session_repl_memory(SessionId, Memory))
|
|
1150
|
+
)
|
|
1151
|
+
; true
|
|
1152
|
+
).
|
|
1153
|
+
|
|
1154
|
+
%% repl_listing(+SessionId, +Filter, -Text)
|
|
1155
|
+
%% Read-only listing used by the meta tool and the `listing.` REPL command.
|
|
1156
|
+
repl_listing(SessionId, Filter, Text) :-
|
|
1157
|
+
with_output_to(string(Text), repl_listing_output(SessionId, Filter)).
|
|
1158
|
+
|
|
1159
|
+
%% repl_listing(+Filter, -Text) uses the current session id.
|
|
1160
|
+
repl_listing(Filter, Text) :-
|
|
1161
|
+
get_session_id(SessionId),
|
|
1162
|
+
repl_listing(SessionId, Filter, Text).
|
|
1163
|
+
|
|
1164
|
+
repl_listing_output(SessionId, Filter) :-
|
|
1165
|
+
( Filter == tools
|
|
1166
|
+
-> forall(session_user_tool_schema(SessionId, Name, Schema),
|
|
1167
|
+
( SessionId:tool_source(Name, Source)
|
|
1168
|
+
-> format("~w - ~w~n~w~n~n", [Name, Schema, Source])
|
|
1169
|
+
; format("~w - ~w~n~n", [Name, Schema])
|
|
1170
|
+
)
|
|
1171
|
+
)
|
|
1172
|
+
; Filter == history
|
|
1173
|
+
-> repl_history_listing(SessionId)
|
|
1174
|
+
; ( current_predicate(SessionId:module_doc/1),
|
|
1175
|
+
SessionId:module_doc(Skill)
|
|
1176
|
+
-> format("skill: ~w~n~n", [Skill])
|
|
1177
|
+
; true
|
|
1178
|
+
),
|
|
1179
|
+
findall(Name/Arity,
|
|
1180
|
+
( current_predicate(SessionId:Name/Arity),
|
|
1181
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
1182
|
+
),
|
|
1183
|
+
Preds0),
|
|
1184
|
+
sort(Preds0, Preds),
|
|
1185
|
+
forall(member(Name/Arity, Preds),
|
|
1186
|
+
( repl_listing_match(Filter, Name, Arity)
|
|
1187
|
+
-> format("~w/~w~n", [Name, Arity]),
|
|
1188
|
+
( current_predicate(SessionId:doc/2),
|
|
1189
|
+
SessionId:doc(Name/Arity, DocText)
|
|
1190
|
+
-> format(" % ~w~n", [DocText])
|
|
1191
|
+
; true
|
|
1192
|
+
),
|
|
1193
|
+
functor(Head, Name, Arity),
|
|
1194
|
+
forall(clause(SessionId:Head, ClauseBody), print_user_clause(Head, ClauseBody))
|
|
1195
|
+
; true
|
|
1196
|
+
)
|
|
1197
|
+
)
|
|
1198
|
+
).
|
|
1199
|
+
|
|
1200
|
+
repl_listing_match(all, _, _) :- !.
|
|
1201
|
+
repl_listing_match(Name/Arity, Name, Arity) :- !.
|
|
1202
|
+
repl_listing_match(Filter, Name, _) :-
|
|
1203
|
+
atom(Filter),
|
|
1204
|
+
!,
|
|
1205
|
+
( sub_atom(Name, 0, _, _, Filter)
|
|
1206
|
+
; sub_atom(Filter, 0, _, _, Name)
|
|
1207
|
+
).
|
|
1208
|
+
repl_listing_match(_, _, _).
|
|
1209
|
+
|
|
1210
|
+
%% repl_user_predicate(+SessionId, +Name, +Arity)
|
|
1211
|
+
%% Excludes reserved and internal predicates from listing/export.
|
|
1212
|
+
repl_user_predicate(_SessionId, Name, _Arity) :-
|
|
1213
|
+
repl_reserved_name(Name),
|
|
1214
|
+
!,
|
|
1215
|
+
fail.
|
|
1216
|
+
repl_user_predicate(_SessionId, Name, _Arity) :-
|
|
1217
|
+
internal_session_predicate(Name),
|
|
1218
|
+
!,
|
|
1219
|
+
fail.
|
|
1220
|
+
repl_user_predicate(_SessionId, Name, _Arity) :-
|
|
1221
|
+
history_predicate(Name),
|
|
1222
|
+
!,
|
|
1223
|
+
fail.
|
|
1224
|
+
repl_user_predicate(SessionId, Name, Arity) :-
|
|
1225
|
+
% User DML clauses are asserted (interpreted) into the session module.
|
|
1226
|
+
% Exclude predicates imported via use_module (e.g. library(clpfd)) and
|
|
1227
|
+
% foreign/builtin predicates. Build a callable head rather than a predicate
|
|
1228
|
+
% indicator: predicate_property/2 wants a goal (Module:Goal).
|
|
1229
|
+
functor(Head, Name, Arity),
|
|
1230
|
+
predicate_property(SessionId:Head, interpreted),
|
|
1231
|
+
\+ predicate_property(SessionId:Head, imported_from(_)),
|
|
1232
|
+
\+ predicate_property(SessionId:Head, foreign).
|
|
1233
|
+
|
|
1234
|
+
internal_session_predicate(param).
|
|
1235
|
+
internal_session_predicate(param_decl).
|
|
1236
|
+
internal_session_predicate(tool_source).
|
|
1237
|
+
internal_session_predicate('__session__').
|
|
1238
|
+
|
|
1239
|
+
%% history_predicate(+Name)
|
|
1240
|
+
%% Runtime-owned harness-history facts. Excluded from the user predicate set (so
|
|
1241
|
+
%% they are neither listed as harness predicates nor exported nor replaced by a
|
|
1242
|
+
%% commit) and protected from user definition -- but CALLABLE from DML, so the
|
|
1243
|
+
%% harness can query what happened in previous sessions.
|
|
1244
|
+
history_predicate(Name) :-
|
|
1245
|
+
atom(Name),
|
|
1246
|
+
sub_atom(Name, 0, _, _, harness_).
|
|
1247
|
+
|
|
1248
|
+
%% repl_reserved_name(+Name)
|
|
1249
|
+
%% Names owned by the REPL runtime. M4 uses repl_reserved/1 to refuse goals
|
|
1250
|
+
%% that try to re-enter or shadow the REPL control surface.
|
|
1251
|
+
%% Any repl_*/meta_* name is reserved (namespace guard), plus the explicit
|
|
1252
|
+
%% control names below. repl_delete is a protected exception for definition
|
|
1253
|
+
%% purposes (see repl_protected_name/1).
|
|
1254
|
+
repl_reserved_name(Name) :-
|
|
1255
|
+
atom(Name),
|
|
1256
|
+
( sub_atom(Name, 0, _, _, repl_)
|
|
1257
|
+
; sub_atom(Name, 0, _, _, meta_)
|
|
1258
|
+
).
|
|
1259
|
+
|
|
1260
|
+
repl_reserved_name(repl_meta_prompt).
|
|
1261
|
+
repl_reserved_name(repl_extract_code).
|
|
1262
|
+
repl_reserved_name(repl_reserved).
|
|
1263
|
+
repl_reserved_name(repl_reserved_name).
|
|
1264
|
+
repl_reserved_name(repl_tool_catalog).
|
|
1265
|
+
repl_reserved_name(repl_listing).
|
|
1266
|
+
repl_reserved_name(repl_assert).
|
|
1267
|
+
repl_reserved_name(repl_retract).
|
|
1268
|
+
repl_reserved_name(repl_delete).
|
|
1269
|
+
repl_reserved_name(repl_freeze).
|
|
1270
|
+
repl_reserved_name(repl_unfreeze).
|
|
1271
|
+
repl_reserved_name(repl_is_frozen).
|
|
1272
|
+
repl_reserved_name(repl_solve).
|
|
1273
|
+
repl_reserved_name(repl_initial_state).
|
|
1274
|
+
repl_reserved_name(repl_user_predicate).
|
|
1275
|
+
repl_reserved_name(query_start).
|
|
1276
|
+
repl_reserved_name(read_repl_goal).
|
|
1277
|
+
repl_reserved_name(create_session).
|
|
1278
|
+
repl_reserved_name(clear_session).
|
|
1279
|
+
repl_reserved_name(destroy_session).
|
|
1280
|
+
repl_reserved_name(create_frame).
|
|
1281
|
+
repl_reserved_name(create_frame_raw).
|
|
1282
|
+
repl_reserved_name(step_frame).
|
|
1283
|
+
repl_reserved_name(end_frame).
|
|
1284
|
+
repl_reserved_name(frame_info).
|
|
1285
|
+
repl_reserved_name(default_frame).
|
|
1286
|
+
repl_reserved_name(load_code).
|
|
1287
|
+
repl_reserved_name(freeze).
|
|
1288
|
+
repl_reserved_name(unfreeze).
|
|
1289
|
+
repl_reserved_name(reset).
|
|
1290
|
+
repl_reserved_name(export).
|
|
1291
|
+
|
|
1292
|
+
%% repl_reserved(+TermOrNameArity)
|
|
1293
|
+
repl_reserved(Name) :-
|
|
1294
|
+
atom(Name),
|
|
1295
|
+
repl_reserved_name(Name).
|
|
1296
|
+
repl_reserved(Name/Arity) :-
|
|
1297
|
+
atom(Name),
|
|
1298
|
+
integer(Arity),
|
|
1299
|
+
repl_reserved_name(Name).
|
|
1300
|
+
repl_reserved(Term) :-
|
|
1301
|
+
compound(Term),
|
|
1302
|
+
functor(Term, Name, _),
|
|
1303
|
+
repl_reserved_name(Name).
|
|
1304
|
+
|
|
1305
|
+
%% ============================================================
|
|
1306
|
+
%% Freeze Lock and Export (M2)
|
|
1307
|
+
%% ============================================================
|
|
1308
|
+
|
|
1309
|
+
%% repl_freeze(+SessionId)
|
|
1310
|
+
%% Makes the living module read-only. Queries, `execute`, and nested task()
|
|
1311
|
+
%% calls still work; only mutation is refused.
|
|
1312
|
+
repl_freeze(SessionId) :-
|
|
1313
|
+
retractall(session_frozen(SessionId, _)),
|
|
1314
|
+
assertz(session_frozen(SessionId, true)).
|
|
1315
|
+
|
|
1316
|
+
%% repl_lock(+SessionId)
|
|
1317
|
+
%% Irreversibly freezes: repl_unfreeze/1 refuses. Used by --locked.
|
|
1318
|
+
repl_lock(SessionId) :-
|
|
1319
|
+
repl_freeze(SessionId),
|
|
1320
|
+
retractall(session_locked(SessionId, _)),
|
|
1321
|
+
assertz(session_locked(SessionId, true)).
|
|
1322
|
+
|
|
1323
|
+
%% repl_unfreeze(+SessionId)
|
|
1324
|
+
repl_unfreeze(SessionId) :-
|
|
1325
|
+
( session_locked(SessionId, true)
|
|
1326
|
+
-> throw(error(permission_error(modify, locked_harness, SessionId),
|
|
1327
|
+
context(repl_unfreeze/1, 'harness is locked; unfreeze is disabled')))
|
|
1328
|
+
; retractall(session_frozen(SessionId, _)),
|
|
1329
|
+
assertz(session_frozen(SessionId, false))
|
|
1330
|
+
).
|
|
1331
|
+
|
|
1332
|
+
%% repl_is_frozen(+SessionId)
|
|
1333
|
+
repl_is_frozen(SessionId) :-
|
|
1334
|
+
session_frozen(SessionId, true),
|
|
1335
|
+
!.
|
|
1336
|
+
|
|
1337
|
+
%% repl_try_unfreeze(+SessionId, -Error)
|
|
1338
|
+
%% Non-throwing wrapper so the host can report a locked harness cleanly.
|
|
1339
|
+
repl_try_unfreeze(SessionId, Error) :-
|
|
1340
|
+
catch(repl_unfreeze(SessionId), UnfreezeError,
|
|
1341
|
+
format(atom(Error), '~w', [UnfreezeError])),
|
|
1342
|
+
( var(Error)
|
|
1343
|
+
-> Error = none
|
|
1344
|
+
; true
|
|
1345
|
+
).
|
|
1346
|
+
|
|
1347
|
+
%% guard_not_frozen(+SessionId)
|
|
1348
|
+
%% Shared mutation guard for assert/retract/consult/load.
|
|
1349
|
+
guard_not_frozen(SessionId) :-
|
|
1350
|
+
( session_frozen(SessionId, true)
|
|
1351
|
+
-> throw(error(permission_error(modify, frozen_harness, SessionId),
|
|
1352
|
+
context(mi_call/3, 'harness is frozen; assert/retract/load are disabled')))
|
|
1353
|
+
; true
|
|
1354
|
+
).
|
|
1355
|
+
|
|
1356
|
+
%% record_directive(+SessionId, +Directive)
|
|
1357
|
+
record_directive(SessionId, Directive) :-
|
|
1358
|
+
( session_directive(SessionId, Directive)
|
|
1359
|
+
-> true
|
|
1360
|
+
; assertz(session_directive(SessionId, Directive))
|
|
1361
|
+
).
|
|
1362
|
+
|
|
1363
|
+
%% ---- Export (serialization) ----
|
|
1364
|
+
|
|
1365
|
+
%% export_harness(+SessionId, +Header, -Text)
|
|
1366
|
+
%% Deterministically serialize the living module into self-contained DML:
|
|
1367
|
+
%% directives, tools, embedded scripts, then user predicates sorted name/arity.
|
|
1368
|
+
export_harness(SessionId, Header, Text) :-
|
|
1369
|
+
with_output_to(string(Text), export_harness_output(SessionId, Header)).
|
|
1370
|
+
|
|
1371
|
+
export_harness_output(SessionId, Header) :-
|
|
1372
|
+
( Header == none
|
|
1373
|
+
-> true
|
|
1374
|
+
; format("% ~w~n", [Header])
|
|
1375
|
+
),
|
|
1376
|
+
export_directives(SessionId),
|
|
1377
|
+
export_tools(SessionId),
|
|
1378
|
+
export_clauses(SessionId).
|
|
1379
|
+
|
|
1380
|
+
export_directives(SessionId) :-
|
|
1381
|
+
findall(Directive, session_directive(SessionId, Directive), Directives0),
|
|
1382
|
+
sort(Directives0, Directives),
|
|
1383
|
+
forall(member(Directive, Directives), format(":- ~q.~n", [Directive])),
|
|
1384
|
+
( Directives == []
|
|
1385
|
+
-> true
|
|
1386
|
+
; nl
|
|
1387
|
+
).
|
|
1388
|
+
|
|
1389
|
+
export_tools(SessionId) :-
|
|
1390
|
+
findall(Name, session_user_tool_schema(SessionId, Name, _), Names0),
|
|
1391
|
+
sort(Names0, Names),
|
|
1392
|
+
forall(member(Name, Names),
|
|
1393
|
+
( SessionId:tool_source(Name, Source)
|
|
1394
|
+
-> format("~w~n~n", [Source])
|
|
1395
|
+
; true
|
|
1396
|
+
)).
|
|
1397
|
+
|
|
1398
|
+
export_clauses(SessionId) :-
|
|
1399
|
+
findall(Name/Arity,
|
|
1400
|
+
( current_predicate(SessionId:Name/Arity),
|
|
1401
|
+
export_user_predicate(SessionId, Name, Arity)
|
|
1402
|
+
),
|
|
1403
|
+
Preds0),
|
|
1404
|
+
sort(Preds0, Preds),
|
|
1405
|
+
forall(member(Name/Arity, Preds),
|
|
1406
|
+
( functor(Head, Name, Arity),
|
|
1407
|
+
forall(clause(SessionId:Head, Body), export_clause(Head, Body))
|
|
1408
|
+
)).
|
|
1409
|
+
|
|
1410
|
+
export_user_predicate(SessionId, Name, Arity) :-
|
|
1411
|
+
repl_user_predicate(SessionId, Name, Arity),
|
|
1412
|
+
\+ tool_predicate_name(Name).
|
|
1413
|
+
|
|
1414
|
+
%% export_clause(+Head, +Body)
|
|
1415
|
+
%% Copy + numbervars the clause so variable names are deterministic (A, B, …),
|
|
1416
|
+
%% which is required for byte-stable export → reload → export.
|
|
1417
|
+
%% export_stats(+SessionId, -Clauses, -Tools, -Scripts)
|
|
1418
|
+
export_stats(SessionId, Clauses, Tools, Scripts) :-
|
|
1419
|
+
findall(Name/Arity,
|
|
1420
|
+
( current_predicate(SessionId:Name/Arity),
|
|
1421
|
+
export_user_predicate(SessionId, Name, Arity)
|
|
1422
|
+
),
|
|
1423
|
+
Preds0),
|
|
1424
|
+
sort(Preds0, Preds),
|
|
1425
|
+
findall(Count,
|
|
1426
|
+
( member(PredName/PredArity, Preds),
|
|
1427
|
+
functor(PredHead, PredName, PredArity),
|
|
1428
|
+
aggregate_all(count, clause(SessionId:PredHead, _), Count)
|
|
1429
|
+
),
|
|
1430
|
+
PredCounts),
|
|
1431
|
+
sum_list(PredCounts, Clauses),
|
|
1432
|
+
findall(Name, session_user_tool_schema(SessionId, Name, _), ToolNames0),
|
|
1433
|
+
sort(ToolNames0, ToolNames),
|
|
1434
|
+
length(ToolNames, Tools),
|
|
1435
|
+
( current_predicate(SessionId:script_file/2)
|
|
1436
|
+
-> findall(Path, SessionId:script_file(_, Path), ScriptPaths0),
|
|
1437
|
+
sort(ScriptPaths0, ScriptPaths),
|
|
1438
|
+
length(ScriptPaths, Scripts)
|
|
1439
|
+
; Scripts = 0
|
|
1440
|
+
).
|
|
1441
|
+
|
|
1442
|
+
export_clause(Head, Body) :-
|
|
1443
|
+
print_user_clause(Head, Body),
|
|
1444
|
+
nl.
|
|
1445
|
+
|
|
1446
|
+
%% format_tool_source(+Head, +Description, +Body, -SourceCode)
|
|
1447
|
+
%% Source text for a tool, used by `listing(tools)` and export. Reverses the
|
|
1448
|
+
%% load-time task/prompt rewrites so readers see `task/N`, not `task_named/3`.
|
|
1449
|
+
format_tool_source(Head, none, Body, SourceCode) :-
|
|
1450
|
+
!,
|
|
1451
|
+
copy_term(Head-Body, NH-NB),
|
|
1452
|
+
numbervars(NH-NB, 0, _),
|
|
1453
|
+
clean_clause_body(NB, CleanBody),
|
|
1454
|
+
( CleanBody == true
|
|
1455
|
+
-> format(string(SourceCode), "tool(~q).", [NH])
|
|
1456
|
+
; format(string(SourceCode), "tool(~q) :-~n ~q.", [NH, CleanBody])
|
|
1457
|
+
).
|
|
1458
|
+
format_tool_source(Head, Description, Body, SourceCode) :-
|
|
1459
|
+
copy_term(Head-Description-Body, NH-ND-NB),
|
|
1460
|
+
numbervars(NH-ND-NB, 0, _),
|
|
1461
|
+
clean_clause_body(NB, CleanBody),
|
|
1462
|
+
( CleanBody == true
|
|
1463
|
+
-> format(string(SourceCode), "tool(~q, ~q).", [NH, ND])
|
|
1464
|
+
; format(string(SourceCode), "tool(~q, ~q) :-~n ~q.", [NH, ND, CleanBody])
|
|
1465
|
+
).
|
|
1466
|
+
|
|
1467
|
+
%% print_user_clause(+Head, +Body)
|
|
1468
|
+
%% Copy + numbervars for deterministic variable names, reverse the load-time
|
|
1469
|
+
%% task/prompt rewrites (see clean_clause_body/2), then print in source form.
|
|
1470
|
+
print_user_clause(Head, Body) :-
|
|
1471
|
+
copy_term(Head-Body, NumberedHead-NumberedBody),
|
|
1472
|
+
numbervars(NumberedHead-NumberedBody, 0, _),
|
|
1473
|
+
clean_clause_body(NumberedBody, CleanBody),
|
|
1474
|
+
( CleanBody == true
|
|
1475
|
+
-> format("~q.~n", [NumberedHead])
|
|
1476
|
+
; format("~q :-~n ~q.~n", [NumberedHead, CleanBody])
|
|
1477
|
+
).
|
|
1478
|
+
|
|
1479
|
+
tool_predicate_name(tool).
|
|
1480
|
+
tool_predicate_name(tool_source).
|
|
1481
|
+
|
|
1482
|
+
%% repl_script_files(+SessionId, -Paths)
|
|
1483
|
+
%% Workspace-relative files linked by script_file/2 facts; the host bundles them.
|
|
1484
|
+
repl_script_files(SessionId, Paths) :-
|
|
1485
|
+
( current_predicate(SessionId:script_file/2)
|
|
1486
|
+
-> % Only literal paths count; a stray variable path is ignored.
|
|
1487
|
+
findall(Path,
|
|
1488
|
+
( SessionId:script_file(_, Path),
|
|
1489
|
+
(atom(Path) ; string(Path))
|
|
1490
|
+
),
|
|
1491
|
+
Paths0),
|
|
1492
|
+
sort(Paths0, Paths)
|
|
1493
|
+
; Paths = []
|
|
1494
|
+
).
|
|
1495
|
+
|
|
1496
|
+
%% repl_materialize_embedded(+SessionId)
|
|
1497
|
+
%% Writes every embedded_file(Path, Content) fact to its workspace path. Used
|
|
1498
|
+
%% after loading a single-file .dml export (where linked scripts are inlined).
|
|
1499
|
+
repl_materialize_embedded(SessionId) :-
|
|
1500
|
+
( current_predicate(SessionId:embedded_file/2)
|
|
1501
|
+
-> forall(SessionId:embedded_file(Path, Content),
|
|
1502
|
+
( resolve_workspace_path(Path, Full),
|
|
1503
|
+
catch(
|
|
1504
|
+
( file_directory_name(Full, Dir),
|
|
1505
|
+
make_directory_path(Dir),
|
|
1506
|
+
setup_call_cleanup(
|
|
1507
|
+
open(Full, write, Stream),
|
|
1508
|
+
format(Stream, "~s", [Content]),
|
|
1509
|
+
close(Stream))
|
|
1510
|
+
),
|
|
1511
|
+
_,
|
|
1512
|
+
true
|
|
1513
|
+
)
|
|
1514
|
+
))
|
|
1515
|
+
; true
|
|
1516
|
+
).
|
|
1517
|
+
|
|
1518
|
+
%% ============================================================
|
|
1519
|
+
%% Prompt Mode (M3)
|
|
1520
|
+
%% ============================================================
|
|
1521
|
+
|
|
1522
|
+
%% repl_protected_name(+Name)
|
|
1523
|
+
%% Reserved names that generated code may not (re)define. repl_delete is a
|
|
1524
|
+
%% legitimate harness directive and is allowed through.
|
|
1525
|
+
repl_protected_name(Name) :-
|
|
1526
|
+
history_predicate(Name).
|
|
1527
|
+
repl_protected_name(Name) :-
|
|
1528
|
+
repl_reserved_name(Name),
|
|
1529
|
+
Name \== repl_delete.
|
|
1530
|
+
|
|
1531
|
+
repl_protected_clause((Head :- _)) :-
|
|
1532
|
+
callable(Head),
|
|
1533
|
+
functor(Head, Name, _),
|
|
1534
|
+
repl_protected_name(Name).
|
|
1535
|
+
repl_protected_clause(Head) :-
|
|
1536
|
+
callable(Head),
|
|
1537
|
+
functor(Head, Name, _),
|
|
1538
|
+
repl_protected_name(Name).
|
|
1539
|
+
|
|
1540
|
+
%% repl_set_meta_catalog(+SessionId, +CatalogText)
|
|
1541
|
+
repl_set_meta_catalog(SessionId, CatalogText) :-
|
|
1542
|
+
retractall(session_meta_catalog(SessionId, _)),
|
|
1543
|
+
assertz(session_meta_catalog(SessionId, CatalogText)).
|
|
1544
|
+
|
|
1545
|
+
%% repl_prepare_single(+SessionId, +ClauseText, -PreparedTerms)
|
|
1546
|
+
%% Reads exactly ONE source term and runs the SAME load-time preprocessing as
|
|
1547
|
+
%% parse_dml_string/2: transform_task_calls/3 (task/N, prompt/N ->
|
|
1548
|
+
%% task_named/3, prompt_named/3), expand_interpolations/3 ({Var} strings), and
|
|
1549
|
+
%% expand_term/2 (e.g. DCG rules). Scratch asserts must be preprocessed exactly
|
|
1550
|
+
%% like a file load, or a clause asserted mid-turn keeps task/N, which has no
|
|
1551
|
+
%% runtime handler and fails silently.
|
|
1552
|
+
%%
|
|
1553
|
+
%% Trailing content is an error so `assert` can never silently drop clauses that
|
|
1554
|
+
%% `assert_many` would have kept.
|
|
1555
|
+
repl_prepare_single(SessionId, ClauseText, PreparedTerms) :-
|
|
1556
|
+
ensure_full_stop(ClauseText, Text1),
|
|
1557
|
+
open_string(Text1, Stream),
|
|
1558
|
+
read_term(Stream, Term, [module(SessionId), variable_names(Bindings)]),
|
|
1559
|
+
( read_term(Stream, Extra, [module(SessionId)]),
|
|
1560
|
+
Extra \== end_of_file
|
|
1561
|
+
-> close(Stream),
|
|
1562
|
+
throw(error(syntax_error('assert expects a single clause; use assert_many for multiple'), _))
|
|
1563
|
+
; close(Stream)
|
|
1564
|
+
),
|
|
1565
|
+
preprocess_loaded_term(Term, Bindings, PreparedTerms).
|
|
1566
|
+
|
|
1567
|
+
%% repl_read_clause(+SessionId, +ClauseText, -Clause)
|
|
1568
|
+
%% Reads exactly one term without preprocessing. Used by retract, which only
|
|
1569
|
+
%% needs the predicate's name/arity. Trailing content is an error.
|
|
1570
|
+
repl_read_clause(SessionId, ClauseText, Clause) :-
|
|
1571
|
+
ensure_full_stop(ClauseText, Text1),
|
|
1572
|
+
open_string(Text1, Stream),
|
|
1573
|
+
read_term(Stream, Clause, [module(SessionId)]),
|
|
1574
|
+
( read_term(Stream, Extra, [module(SessionId)]),
|
|
1575
|
+
Extra \== end_of_file
|
|
1576
|
+
-> close(Stream),
|
|
1577
|
+
throw(error(syntax_error('retract expects a single clause pattern'), _))
|
|
1578
|
+
; close(Stream)
|
|
1579
|
+
).
|
|
1580
|
+
|
|
1581
|
+
%% repl_head_of(+Term, -Head)
|
|
1582
|
+
%% Strips a rule body, so retracting with a full clause pattern targets the
|
|
1583
|
+
%% predicate instead of accidentally matching every rule ((:-)/2).
|
|
1584
|
+
repl_head_of((Head :- _Body), Head) :- !.
|
|
1585
|
+
repl_head_of(Head, Head).
|
|
1586
|
+
|
|
1587
|
+
%% repl_assert_clause(+PreparedTerm, +SessionId)
|
|
1588
|
+
%% Asserts one already-preprocessed term, rejecting reserved names.
|
|
1589
|
+
repl_assert_clause(PreparedTerm, SessionId) :-
|
|
1590
|
+
( repl_protected_clause(PreparedTerm)
|
|
1591
|
+
-> throw(error(permission_error(modify, reserved_name, PreparedTerm),
|
|
1592
|
+
context(repl_assert/3, 'cannot define a reserved predicate')))
|
|
1593
|
+
; process_clause(PreparedTerm, SessionId)
|
|
1594
|
+
).
|
|
1595
|
+
|
|
1596
|
+
%% repl_assert(+SessionId, +ClauseText, -Result)
|
|
1597
|
+
%% Parses and asserts generated DML with the load-time preprocessing applied,
|
|
1598
|
+
%% so scratch-asserted clauses behave exactly like loaded ones.
|
|
1599
|
+
%% Refused when frozen; reserved names are rejected.
|
|
1600
|
+
repl_assert(SessionId, ClauseText, Result) :-
|
|
1601
|
+
catch(
|
|
1602
|
+
( guard_not_frozen(SessionId),
|
|
1603
|
+
repl_prepare_single(SessionId, ClauseText, PreparedTerms),
|
|
1604
|
+
forall(member(PreparedTerm, PreparedTerms),
|
|
1605
|
+
repl_assert_clause(PreparedTerm, SessionId)),
|
|
1606
|
+
Result = ok
|
|
1607
|
+
),
|
|
1608
|
+
AssertError,
|
|
1609
|
+
format(atom(Result), 'error(~w)', [AssertError])
|
|
1610
|
+
).
|
|
1611
|
+
|
|
1612
|
+
%% repl_set_dml_reference(+SessionId, +Text)
|
|
1613
|
+
%% Stores the bundled DML reference so the meta prompt can include it.
|
|
1614
|
+
repl_set_dml_reference(SessionId, Text) :-
|
|
1615
|
+
retractall(session_dml_reference(SessionId, _)),
|
|
1616
|
+
assertz(session_dml_reference(SessionId, Text)).
|
|
1617
|
+
|
|
1618
|
+
%% repl_set_authoring_examples(+SessionId, +Text)
|
|
1619
|
+
%% Stores the curated authoring patterns for the meta prompt.
|
|
1620
|
+
repl_set_authoring_examples(SessionId, Text) :-
|
|
1621
|
+
retractall(session_authoring_examples(SessionId, _)),
|
|
1622
|
+
assertz(session_authoring_examples(SessionId, Text)).
|
|
1623
|
+
|
|
1624
|
+
%% repl_set_meta_template(+SessionId, +Template)
|
|
1625
|
+
%% Stores the meta-prompt template (the bundled META_PROMPT.md). Placeholders
|
|
1626
|
+
%% {{CATALOG}}, {{REFERENCE}}, {{EXAMPLES}}, {{MANIFEST}}, {{MEMORY}}, {{REQUEST}}
|
|
1627
|
+
%% are filled by repl_meta_prompt/3.
|
|
1628
|
+
repl_set_meta_template(SessionId, Template) :-
|
|
1629
|
+
retractall(session_meta_template(SessionId, _)),
|
|
1630
|
+
assertz(session_meta_template(SessionId, Template)).
|
|
1631
|
+
|
|
1632
|
+
%% repl_set_harness_examples(+SessionId, +Text)
|
|
1633
|
+
%% Stores the bundled HARNESS_EXAMPLES.md ({{EXAMPLES}}).
|
|
1634
|
+
repl_set_harness_examples(SessionId, Text) :-
|
|
1635
|
+
retractall(session_harness_examples(SessionId, _)),
|
|
1636
|
+
assertz(session_harness_examples(SessionId, Text)).
|
|
1637
|
+
|
|
1638
|
+
%% repl_set_prolog_snippets(+SessionId, +Text)
|
|
1639
|
+
%% Stores the bundled PROLOG_SNIPPETS.md ({{SNIPPETS}}).
|
|
1640
|
+
repl_set_prolog_snippets(SessionId, Text) :-
|
|
1641
|
+
retractall(session_prolog_snippets(SessionId, _)),
|
|
1642
|
+
assertz(session_prolog_snippets(SessionId, Text)).
|
|
1643
|
+
|
|
1644
|
+
%% repl_parse_clauses(+SessionId, +ClausesText, -Terms)
|
|
1645
|
+
%% Parse and preprocess a block of DML clauses without asserting them.
|
|
1646
|
+
repl_parse_clauses(SessionId, ClausesText, Terms) :-
|
|
1647
|
+
ensure_full_stop(ClausesText, Text1),
|
|
1648
|
+
open_string(Text1, Stream),
|
|
1649
|
+
repl_read_prepared_terms(Stream, SessionId, Terms),
|
|
1650
|
+
close(Stream).
|
|
1651
|
+
|
|
1652
|
+
repl_read_prepared_terms(Stream, SessionId, Terms) :-
|
|
1653
|
+
read_term(Stream, Term, [module(SessionId), variable_names(Bindings)]),
|
|
1654
|
+
( Term == end_of_file
|
|
1655
|
+
-> Terms = []
|
|
1656
|
+
; % Apply the same preprocessing as parse_dml_string/2 so scratch clauses
|
|
1657
|
+
% match loaded ones (task/prompt rewrite, interpolation, expand_term).
|
|
1658
|
+
preprocess_loaded_term(Term, Bindings, Prepared),
|
|
1659
|
+
% Directives (e.g. use_module(library(clpfd))) must run while parsing so
|
|
1660
|
+
% their operators are visible to the terms that follow; keep them out of
|
|
1661
|
+
% the returned list so repl_commit_terms does not process them twice.
|
|
1662
|
+
partition_directives(Prepared, Rules, Directives),
|
|
1663
|
+
forall(member(Directive, Directives), process_clause(Directive, SessionId)),
|
|
1664
|
+
append(Rules, Rest, Terms),
|
|
1665
|
+
repl_read_prepared_terms(Stream, SessionId, Rest)
|
|
1666
|
+
).
|
|
1667
|
+
|
|
1668
|
+
partition_directives([], [], []).
|
|
1669
|
+
partition_directives([Term|Terms], Rules, Directives) :-
|
|
1670
|
+
( Term = (:- _)
|
|
1671
|
+
-> Rules = Rules0,
|
|
1672
|
+
Directives = [Term|Directives0]
|
|
1673
|
+
; Rules = [Term|Rules0],
|
|
1674
|
+
Directives = Directives0
|
|
1675
|
+
),
|
|
1676
|
+
partition_directives(Terms, Rules0, Directives0).
|
|
1677
|
+
|
|
1678
|
+
%% repl_commit_terms(+SessionId, +Terms)
|
|
1679
|
+
%% Assert already-preprocessed terms (the shared tail of assert_many/define).
|
|
1680
|
+
repl_commit_terms(SessionId, Terms) :-
|
|
1681
|
+
forall(member(PreparedTerm, Terms),
|
|
1682
|
+
( repl_protected_clause(PreparedTerm)
|
|
1683
|
+
-> throw(error(permission_error(modify, reserved_name, PreparedTerm),
|
|
1684
|
+
context(repl_assert_many/3, 'cannot define a reserved predicate')))
|
|
1685
|
+
; process_clause(PreparedTerm, SessionId)
|
|
1686
|
+
)).
|
|
1687
|
+
|
|
1688
|
+
%% repl_assert_many(+SessionId, +ClausesText, -Result)
|
|
1689
|
+
%% Parses a block of clauses and asserts them in the order written. Prefer this
|
|
1690
|
+
%% for defining a predicate: clause order matters for backtracking, so asserting
|
|
1691
|
+
%% one clause at a time can produce a wrong program.
|
|
1692
|
+
repl_assert_many(SessionId, ClausesText, Result) :-
|
|
1693
|
+
catch(
|
|
1694
|
+
( guard_not_frozen(SessionId),
|
|
1695
|
+
repl_parse_clauses(SessionId, ClausesText, Terms),
|
|
1696
|
+
repl_commit_terms(SessionId, Terms),
|
|
1697
|
+
repl_note_touched(SessionId, Terms),
|
|
1698
|
+
Result = ok
|
|
1699
|
+
),
|
|
1700
|
+
AssertError,
|
|
1701
|
+
format(atom(Result), 'error(~w)', [AssertError])
|
|
1702
|
+
).
|
|
1703
|
+
|
|
1704
|
+
%% repl_term_indicator(+Term, -Name/Arity)
|
|
1705
|
+
%% The plain predicate a parsed clause defines. Directives and tool/2 wrappers
|
|
1706
|
+
%% have no plain indicator here (process_clause owns them).
|
|
1707
|
+
repl_term_indicator((:- _), _) :- !, fail.
|
|
1708
|
+
repl_term_indicator((tool(_, _) :- _), _) :- !, fail.
|
|
1709
|
+
repl_term_indicator((tool(_) :- _), _) :- !, fail.
|
|
1710
|
+
repl_term_indicator(Term, Name/Arity) :-
|
|
1711
|
+
repl_head_of(Term, Head),
|
|
1712
|
+
callable(Head),
|
|
1713
|
+
functor(Head, Name, Arity).
|
|
1714
|
+
|
|
1715
|
+
%% repl_note_touched(+SessionId, +Terms)
|
|
1716
|
+
%% Record the predicate indicators a term list defines, for history recording.
|
|
1717
|
+
repl_note_touched(SessionId, Terms) :-
|
|
1718
|
+
forall(( member(Term, Terms), repl_term_indicator(Term, Name/Arity) ),
|
|
1719
|
+
( session_edit_touched(SessionId, Name/Arity)
|
|
1720
|
+
-> true
|
|
1721
|
+
; assertz(session_edit_touched(SessionId, Name/Arity))
|
|
1722
|
+
)).
|
|
1723
|
+
|
|
1724
|
+
%% repl_edit_touched(+SessionId, -Indicators)
|
|
1725
|
+
%% Predicates touched by edit tools since the last clear (sorted, unique).
|
|
1726
|
+
repl_edit_touched(SessionId, Indicators) :-
|
|
1727
|
+
findall(Name/Arity, session_edit_touched(SessionId, Name/Arity), Raw),
|
|
1728
|
+
sort(Raw, Indicators).
|
|
1729
|
+
|
|
1730
|
+
%% repl_edit_touched_text(+SessionId, -Text)
|
|
1731
|
+
%% "Name/Arity, Name/Arity" of the predicates an agent turn touched.
|
|
1732
|
+
repl_edit_touched_text(SessionId, Text) :-
|
|
1733
|
+
repl_edit_touched(SessionId, Indicators),
|
|
1734
|
+
with_output_to(string(Text), print_pred_list(Indicators)).
|
|
1735
|
+
|
|
1736
|
+
repl_edit_touched_clear(SessionId) :-
|
|
1737
|
+
retractall(session_edit_touched(SessionId, _)).
|
|
1738
|
+
|
|
1739
|
+
%% repl_callable_indicator(+Head, -Name, -Arity)
|
|
1740
|
+
%% Validate a predicate head: an ordinary (non-operator, non-system) functor.
|
|
1741
|
+
repl_callable_indicator(Head, Name, Arity) :-
|
|
1742
|
+
callable(Head),
|
|
1743
|
+
functor(Head, Name, Arity),
|
|
1744
|
+
atom(Name),
|
|
1745
|
+
Name \== '[]',
|
|
1746
|
+
Name \== '{}',
|
|
1747
|
+
\+ repl_system_functor(Name/Arity).
|
|
1748
|
+
|
|
1749
|
+
%% Functors that are list/operator/system structure, never user predicates.
|
|
1750
|
+
repl_system_functor(Name/_) :-
|
|
1751
|
+
member(Name, ['.', ',', ';', ':-', '->', ':', '|', '!', '\\+', '=', 'is']).
|
|
1752
|
+
|
|
1753
|
+
%% repl_retract(+SessionId, +ClausePattern, -Result)
|
|
1754
|
+
%% Retracts matching user clauses. Reserved names are protected.
|
|
1755
|
+
repl_retract(SessionId, ClauseText, Result) :-
|
|
1756
|
+
catch(
|
|
1757
|
+
( guard_not_frozen(SessionId),
|
|
1758
|
+
repl_read_clause(SessionId, ClauseText, Pattern),
|
|
1759
|
+
repl_head_of(Pattern, Clause),
|
|
1760
|
+
repl_callable_indicator(Clause, Name, Arity),
|
|
1761
|
+
( repl_protected_clause(Clause)
|
|
1762
|
+
-> throw(error(permission_error(modify, reserved_name, Clause),
|
|
1763
|
+
context(repl_retract/3, 'cannot retract a reserved predicate')))
|
|
1764
|
+
; true
|
|
1765
|
+
),
|
|
1766
|
+
functor(Head, Name, Arity),
|
|
1767
|
+
retractall(SessionId:Head),
|
|
1768
|
+
( session_user_tools(SessionId, Name)
|
|
1769
|
+
-> retractall(session_user_tools(SessionId, Name)),
|
|
1770
|
+
retractall(session_user_tool_schema(SessionId, Name, _)),
|
|
1771
|
+
retractall(SessionId:tool_source(Name, _))
|
|
1772
|
+
; true
|
|
1773
|
+
),
|
|
1774
|
+
( session_edit_touched(SessionId, Name/Arity)
|
|
1775
|
+
-> true
|
|
1776
|
+
; assertz(session_edit_touched(SessionId, Name/Arity))
|
|
1777
|
+
),
|
|
1778
|
+
Result = ok
|
|
1779
|
+
),
|
|
1780
|
+
RetractError,
|
|
1781
|
+
format(atom(Result), 'error(~w)', [RetractError])
|
|
1782
|
+
).
|
|
1783
|
+
|
|
1784
|
+
%% repl_delete_predicate(+SessionId, +Name, +Arity, -Result)
|
|
1785
|
+
%% Fully remove a predicate. Unlike retract (which only removes matching
|
|
1786
|
+
%% clauses and can leave an empty predicate behind), this abolishes it.
|
|
1787
|
+
repl_delete_predicate(SessionId, Name, Arity, Result) :-
|
|
1788
|
+
catch(
|
|
1789
|
+
( guard_not_frozen(SessionId),
|
|
1790
|
+
atom(Name), integer(Arity), Arity >= 0,
|
|
1791
|
+
( repl_protected_name(Name)
|
|
1792
|
+
-> throw(error(permission_error(modify, reserved_name, Name/Arity),
|
|
1793
|
+
context(repl_delete_predicate/4, 'cannot delete a reserved predicate')))
|
|
1794
|
+
; true
|
|
1795
|
+
),
|
|
1796
|
+
catch(abolish(SessionId:Name/Arity), _, true),
|
|
1797
|
+
( session_user_tools(SessionId, Name)
|
|
1798
|
+
-> retractall(session_user_tools(SessionId, Name)),
|
|
1799
|
+
retractall(session_user_tool_schema(SessionId, Name, _)),
|
|
1800
|
+
retractall(SessionId:tool_source(Name, _))
|
|
1801
|
+
; true
|
|
1802
|
+
),
|
|
1803
|
+
( session_edit_touched(SessionId, Name/Arity)
|
|
1804
|
+
-> true
|
|
1805
|
+
; assertz(session_edit_touched(SessionId, Name/Arity))
|
|
1806
|
+
),
|
|
1807
|
+
Result = ok
|
|
1808
|
+
),
|
|
1809
|
+
DeleteError,
|
|
1810
|
+
format(atom(Result), 'error(~w)', [DeleteError])
|
|
1811
|
+
).
|
|
1812
|
+
|
|
1813
|
+
%% repl_define(+SessionId, +HeadPatternText, +ClausesText, -Result)
|
|
1814
|
+
%% Replace every predicate the clause block defines. It parses the block,
|
|
1815
|
+
%% computes its indicators (plus the head pattern's), abolishes each, then
|
|
1816
|
+
%% asserts the block. This makes `define` safe no matter how many predicates a
|
|
1817
|
+
%% block spans, so a multi-predicate block cannot append duplicate clauses.
|
|
1818
|
+
repl_define(SessionId, HeadPatternText, ClausesText, Result) :-
|
|
1819
|
+
catch(
|
|
1820
|
+
( guard_not_frozen(SessionId),
|
|
1821
|
+
repl_read_clause(SessionId, HeadPatternText, Pattern),
|
|
1822
|
+
repl_head_of(Pattern, HeadClause),
|
|
1823
|
+
repl_callable_indicator(HeadClause, HeadName, HeadArity),
|
|
1824
|
+
repl_parse_clauses(SessionId, ClausesText, Terms),
|
|
1825
|
+
findall(Name/Arity,
|
|
1826
|
+
( member(Term, Terms), repl_term_indicator(Term, Name/Arity) ),
|
|
1827
|
+
BlockIndicators0),
|
|
1828
|
+
sort([HeadName/HeadArity|BlockIndicators0], Indicators),
|
|
1829
|
+
forall(member(Name/Arity, Indicators),
|
|
1830
|
+
( repl_protected_name(Name)
|
|
1831
|
+
-> throw(error(permission_error(modify, reserved_name, Name/Arity),
|
|
1832
|
+
context(repl_define/4, 'cannot define a reserved predicate')))
|
|
1833
|
+
; catch(abolish(SessionId:Name/Arity), _, true),
|
|
1834
|
+
( session_user_tools(SessionId, Name)
|
|
1835
|
+
-> retractall(session_user_tools(SessionId, Name)),
|
|
1836
|
+
retractall(session_user_tool_schema(SessionId, Name, _)),
|
|
1837
|
+
retractall(SessionId:tool_source(Name, _))
|
|
1838
|
+
; true
|
|
1839
|
+
)
|
|
1840
|
+
)),
|
|
1841
|
+
repl_commit_terms(SessionId, Terms),
|
|
1842
|
+
repl_note_touched(SessionId, Terms),
|
|
1843
|
+
Result = ok
|
|
1844
|
+
),
|
|
1845
|
+
DefineError,
|
|
1846
|
+
format(atom(Result), 'error(~w)', [DefineError])
|
|
1847
|
+
).
|
|
1848
|
+
|
|
1849
|
+
%% repl_extract_code(+Text, -Source)
|
|
1850
|
+
%% Extracts a ```dml fenced block; falls back to the whole answer.
|
|
1851
|
+
repl_extract_code(Text, Source) :-
|
|
1852
|
+
atom_string(TextAtom, Text),
|
|
1853
|
+
( sub_string(TextAtom, Start0, _, _, "```dml")
|
|
1854
|
+
-> Start is Start0 + 6,
|
|
1855
|
+
% After = 0 takes the remainder of the text (an unbound After would
|
|
1856
|
+
% bind Rest to the empty prefix first).
|
|
1857
|
+
sub_string(TextAtom, Start, _, 0, Rest),
|
|
1858
|
+
% Search for the closing fence by offset: binding Before=0 here would
|
|
1859
|
+
% require the fence to be at the very start of Rest.
|
|
1860
|
+
( sub_string(Rest, FenceEnd, _, _, "```")
|
|
1861
|
+
-> sub_string(Rest, 0, FenceEnd, _, Source)
|
|
1862
|
+
; Source = Rest
|
|
1863
|
+
)
|
|
1864
|
+
; Source = TextAtom
|
|
1865
|
+
).
|
|
1866
|
+
|
|
1867
|
+
%% repl_manifest(+SessionId, -ManifestText)
|
|
1868
|
+
%% Compact signatures + tools + entries + directives for the meta prompt.
|
|
1869
|
+
repl_manifest(SessionId, Manifest) :-
|
|
1870
|
+
findall(Name/Arity,
|
|
1871
|
+
( current_predicate(SessionId:Name/Arity),
|
|
1872
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
1873
|
+
),
|
|
1874
|
+
Preds0),
|
|
1875
|
+
sort(Preds0, Preds),
|
|
1876
|
+
findall(Name, session_user_tool_schema(SessionId, Name, _), Tools0),
|
|
1877
|
+
sort(Tools0, Tools),
|
|
1878
|
+
findall(Directive, session_directive(SessionId, Directive), Directives0),
|
|
1879
|
+
sort(Directives0, Directives),
|
|
1880
|
+
( current_predicate(SessionId:module_doc/1),
|
|
1881
|
+
SessionId:module_doc(Summary0)
|
|
1882
|
+
-> Summary = Summary0
|
|
1883
|
+
; Summary = ""
|
|
1884
|
+
),
|
|
1885
|
+
( current_predicate(SessionId:doc/2)
|
|
1886
|
+
-> findall(PI-DocText, SessionId:doc(PI, DocText), Docs0)
|
|
1887
|
+
; Docs0 = []
|
|
1888
|
+
),
|
|
1889
|
+
repl_history_summary(SessionId, HistorySummary),
|
|
1890
|
+
with_output_to(string(Manifest),
|
|
1891
|
+
( ( Summary == ""
|
|
1892
|
+
-> format("- skill: (none; add module_doc/1 so the harness describes itself and can be routed)~n")
|
|
1893
|
+
; format("- skill: ~w~n", [Summary])
|
|
1894
|
+
),
|
|
1895
|
+
format("- predicates: ~w~n", [Preds]),
|
|
1896
|
+
format("- tools: ~w~n", [Tools]),
|
|
1897
|
+
format("- directives: ~w~n", [Directives]),
|
|
1898
|
+
( Docs0 == []
|
|
1899
|
+
-> format("- docs: (none; add doc(Name/Arity, Text) facts)~n")
|
|
1900
|
+
; forall(member(PI-DocText, Docs0), format("- doc(~w): ~w~n", [PI, DocText]))
|
|
1901
|
+
),
|
|
1902
|
+
format("- history: ~w~n", [HistorySummary])
|
|
1903
|
+
)).
|
|
1904
|
+
|
|
1905
|
+
%% repl_meta_memory_text(+SessionId, -Text)
|
|
1906
|
+
repl_meta_memory_text(SessionId, Text) :-
|
|
1907
|
+
( session_meta_memory(SessionId, Memory)
|
|
1908
|
+
-> true
|
|
1909
|
+
; Memory = []
|
|
1910
|
+
),
|
|
1911
|
+
last_n(Memory, 10, Recent),
|
|
1912
|
+
with_output_to(string(Text),
|
|
1913
|
+
forall(member(message{role: Role, content: Content}, Recent),
|
|
1914
|
+
format("~w: ~w~n", [Role, Content]))).
|
|
1915
|
+
|
|
1916
|
+
%% repl_memory_term(+SessionId, +Slot, -TermText)
|
|
1917
|
+
%% Serializes a live memory slot (harness|meta) as a Prolog term so a session
|
|
1918
|
+
%% can persist it and restore it on `--session` resume.
|
|
1919
|
+
repl_memory_term(SessionId, Slot, Text) :-
|
|
1920
|
+
( Slot == meta
|
|
1921
|
+
-> ( session_meta_memory(SessionId, Memory)
|
|
1922
|
+
-> true
|
|
1923
|
+
; Memory = []
|
|
1924
|
+
)
|
|
1925
|
+
; ( session_repl_memory(SessionId, Memory)
|
|
1926
|
+
-> true
|
|
1927
|
+
; Memory = []
|
|
1928
|
+
)
|
|
1929
|
+
),
|
|
1930
|
+
with_output_to(string(Text),
|
|
1931
|
+
write_term(Memory, [quoted(true), max_depth(0)])).
|
|
1932
|
+
|
|
1933
|
+
last_n(List, N, Tail) :-
|
|
1934
|
+
length(List, Len),
|
|
1935
|
+
( Len =< N
|
|
1936
|
+
-> Tail = List
|
|
1937
|
+
; Skip is Len - N,
|
|
1938
|
+
length(Prefix, Skip),
|
|
1939
|
+
append(Prefix, Tail, List)
|
|
1940
|
+
).
|
|
1941
|
+
|
|
1942
|
+
%% ============================================================
|
|
1943
|
+
%% Harness history (see docs/DML_REPL_SESSION_HISTORY.md)
|
|
1944
|
+
%% ============================================================
|
|
1945
|
+
|
|
1946
|
+
%% repl_history_load(+SessionId, +Text)
|
|
1947
|
+
%% Asserts harness_* history facts (the persisted .deepclause/repl/history.dml)
|
|
1948
|
+
%% into the session module so the harness can query prior sessions.
|
|
1949
|
+
repl_history_load(SessionId, Text) :-
|
|
1950
|
+
( var(Text)
|
|
1951
|
+
-> true
|
|
1952
|
+
; open_string(Text, Stream),
|
|
1953
|
+
repl_history_read_stream(Stream, SessionId),
|
|
1954
|
+
close(Stream)
|
|
1955
|
+
).
|
|
1956
|
+
|
|
1957
|
+
repl_history_read_stream(Stream, SessionId) :-
|
|
1958
|
+
catch(read_term(Stream, Term, [module(SessionId)]), _, Term = end_of_file),
|
|
1959
|
+
( Term == end_of_file
|
|
1960
|
+
-> true
|
|
1961
|
+
; repl_history_assert_term(Term, SessionId),
|
|
1962
|
+
repl_history_read_stream(Stream, SessionId)
|
|
1963
|
+
).
|
|
1964
|
+
|
|
1965
|
+
%% repl_history_record(+SessionId, +FactText)
|
|
1966
|
+
%% Asserts one or more history facts into the live module. The recorder persists
|
|
1967
|
+
%% the same facts to history.dml; here they become queryable.
|
|
1968
|
+
repl_history_record(SessionId, FactText) :-
|
|
1969
|
+
( var(FactText)
|
|
1970
|
+
-> true
|
|
1971
|
+
; ensure_full_stop(FactText, Text1),
|
|
1972
|
+
open_string(Text1, Stream),
|
|
1973
|
+
repl_history_read_stream(Stream, SessionId),
|
|
1974
|
+
close(Stream)
|
|
1975
|
+
).
|
|
1976
|
+
|
|
1977
|
+
%% Only harness_* facts may be asserted through this path.
|
|
1978
|
+
repl_history_assert_term(Term, SessionId) :-
|
|
1979
|
+
( nonvar(Term),
|
|
1980
|
+
Term \== end_of_file,
|
|
1981
|
+
Term \== none,
|
|
1982
|
+
callable(Term),
|
|
1983
|
+
functor(Term, Name, _),
|
|
1984
|
+
history_predicate(Name)
|
|
1985
|
+
-> % harness_head is a singleton: keep the latest (rollback moves it back).
|
|
1986
|
+
( Name == harness_head
|
|
1987
|
+
-> retractall(SessionId:harness_head(_, _, _, _))
|
|
1988
|
+
; true
|
|
1989
|
+
),
|
|
1990
|
+
% Migrate pre-/5 harness_change facts (they carried an unused Entry arg).
|
|
1991
|
+
( Term = harness_change(V, Ts, Kind, Sid, Turn, _Entry)
|
|
1992
|
+
-> assertz(SessionId:harness_change(V, Ts, Kind, Sid, Turn))
|
|
1993
|
+
; assertz(SessionId:Term)
|
|
1994
|
+
)
|
|
1995
|
+
; true
|
|
1996
|
+
).
|
|
1997
|
+
|
|
1998
|
+
%% repl_rollback(+SessionId, +Source, -Result)
|
|
1999
|
+
%% Restores the living module from a history version's serialized source:
|
|
2000
|
+
%% abolishes the user predicates and reloads the source (directives, tools and
|
|
2001
|
+
%% entries come from the source too). Refused when frozen. Runtime conversation
|
|
2002
|
+
%% memory is preserved.
|
|
2003
|
+
repl_rollback(SessionId, Source, Result) :-
|
|
2004
|
+
catch(
|
|
2005
|
+
( guard_not_frozen(SessionId),
|
|
2006
|
+
repl_abolish_user_predicates(SessionId),
|
|
2007
|
+
retractall(session_directive(SessionId, _)),
|
|
2008
|
+
retractall(session_user_tools(SessionId, _)),
|
|
2009
|
+
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
2010
|
+
( current_predicate(SessionId:tool_source/2)
|
|
2011
|
+
-> retractall(SessionId:tool_source(_, _))
|
|
2012
|
+
; true
|
|
2013
|
+
),
|
|
2014
|
+
( Source == ""
|
|
2015
|
+
-> Result = ok
|
|
2016
|
+
; load_code(SessionId, Source, LoadError),
|
|
2017
|
+
( LoadError == none
|
|
2018
|
+
-> Result = ok
|
|
2019
|
+
; format(atom(Result), 'error(~w)', [LoadError])
|
|
2020
|
+
)
|
|
2021
|
+
)
|
|
2022
|
+
),
|
|
2023
|
+
RollbackError,
|
|
2024
|
+
format(atom(Result), 'error(~w)', [RollbackError])
|
|
2025
|
+
).
|
|
2026
|
+
|
|
2027
|
+
%% repl_user_predicates(+SessionId, -Preds)
|
|
2028
|
+
%% Sorted Name/Arity list of the harness's own predicates.
|
|
2029
|
+
repl_user_predicates(SessionId, Preds) :-
|
|
2030
|
+
findall(Name/Arity,
|
|
2031
|
+
( current_predicate(SessionId:Name/Arity),
|
|
2032
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
2033
|
+
),
|
|
2034
|
+
Preds0),
|
|
2035
|
+
sort(Preds0, Preds).
|
|
2036
|
+
|
|
2037
|
+
%% repl_user_predicates_text(+SessionId, -Text)
|
|
2038
|
+
%% Rendered "name/arity, name/arity" for change diffing on the host side.
|
|
2039
|
+
repl_user_predicates_text(SessionId, Text) :-
|
|
2040
|
+
repl_user_predicates(SessionId, Preds),
|
|
2041
|
+
with_output_to(string(Text), print_pred_list(Preds)).
|
|
2042
|
+
|
|
2043
|
+
%% repl_predicate_arities(+SessionId, +Name, -Arities)
|
|
2044
|
+
%% Sorted arities of a user predicate, e.g. agent_main -> [0,1,2]. Used by
|
|
2045
|
+
%% `deepclause run` to pick the agent_main/N to call for the given arguments.
|
|
2046
|
+
repl_predicate_arities(SessionId, Name, Arities) :-
|
|
2047
|
+
atom(Name),
|
|
2048
|
+
findall(Arity,
|
|
2049
|
+
( current_predicate(SessionId:Name/Arity),
|
|
2050
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
2051
|
+
),
|
|
2052
|
+
Arities0),
|
|
2053
|
+
sort(Arities0, Arities).
|
|
2054
|
+
|
|
2055
|
+
%% repl_history_listing(+SessionId)
|
|
2056
|
+
%% Human-readable tail for `listing(history)`.
|
|
2057
|
+
repl_history_listing(SessionId) :-
|
|
2058
|
+
( current_predicate(SessionId:harness_head/4),
|
|
2059
|
+
SessionId:harness_head(Version, Timestamp, HeadSession, _Turn)
|
|
2060
|
+
-> format("harness_head(~w, ~w, ~w)~n", [Version, Timestamp, HeadSession])
|
|
2061
|
+
; format("harness_head(none)~n")
|
|
2062
|
+
),
|
|
2063
|
+
( current_predicate(SessionId:harness_session/5)
|
|
2064
|
+
-> forall(SessionId:harness_session(Sid, Started, Model, Backend, Dir),
|
|
2065
|
+
format("harness_session(~w, ~w, ~w, ~w, ~w)~n", [Sid, Started, Model, Backend, Dir]))
|
|
2066
|
+
; true
|
|
2067
|
+
),
|
|
2068
|
+
( current_predicate(SessionId:harness_change/5)
|
|
2069
|
+
-> forall(SessionId:harness_change(V, Ts, Kind, Sid, Turn),
|
|
2070
|
+
format("harness_change(~w, ~w, ~w, ~w, ~w)~n", [V, Ts, Kind, Sid, Turn]))
|
|
2071
|
+
; true
|
|
2072
|
+
).
|
|
2073
|
+
|
|
2074
|
+
%% repl_history_summary(+SessionId, -Text)
|
|
2075
|
+
%% One-line history for the meta prompt manifest.
|
|
2076
|
+
repl_history_summary(SessionId, Text) :-
|
|
2077
|
+
( current_predicate(SessionId:harness_head/4),
|
|
2078
|
+
SessionId:harness_head(Version, Timestamp, HeadSession, _Turn)
|
|
2079
|
+
-> true
|
|
2080
|
+
; Version = none, Timestamp = none, HeadSession = none
|
|
2081
|
+
),
|
|
2082
|
+
( current_predicate(SessionId:harness_session/5)
|
|
2083
|
+
-> findall(s, SessionId:harness_session(_, _, _, _, _), Sessions0),
|
|
2084
|
+
length(Sessions0, Sessions)
|
|
2085
|
+
; Sessions = 0
|
|
2086
|
+
),
|
|
2087
|
+
( current_predicate(SessionId:harness_change/5)
|
|
2088
|
+
-> findall(c, SessionId:harness_change(_, _, _, _, _), Changes0),
|
|
2089
|
+
length(Changes0, Changes)
|
|
2090
|
+
; Changes = 0
|
|
2091
|
+
),
|
|
2092
|
+
format(atom(Text), 'version ~w (updated ~w by ~w), ~w session(s), ~w change(s)',
|
|
2093
|
+
[Version, Timestamp, HeadSession, Sessions, Changes]).
|
|
2094
|
+
|
|
2095
|
+
%% repl_meta_prompt(+SessionId, +Request, -PromptText)
|
|
2096
|
+
%% Assembles the deterministic code-writing prompt: authoring contract, host
|
|
2097
|
+
%% tool catalog, harness manifest, recent meta memory, the request, and the
|
|
2098
|
+
%% output contract.
|
|
2099
|
+
repl_meta_prompt(SessionId, Request, Prompt) :-
|
|
2100
|
+
( session_meta_catalog(SessionId, Catalog) -> true ; Catalog = "" ),
|
|
2101
|
+
( session_dml_reference(SessionId, Reference) -> true ; Reference = "" ),
|
|
2102
|
+
( session_authoring_examples(SessionId, Authoring) -> true ; Authoring = "" ),
|
|
2103
|
+
( session_harness_examples(SessionId, Examples) -> true ; Examples = "" ),
|
|
2104
|
+
( session_prolog_snippets(SessionId, Snippets) -> true ; Snippets = "" ),
|
|
2105
|
+
repl_manifest(SessionId, Manifest),
|
|
2106
|
+
repl_meta_memory_text(SessionId, MemoryText),
|
|
2107
|
+
% The template is the bundled META_PROMPT.md, injected by the host via
|
|
2108
|
+
% repl_set_meta_template/2. The fallback keeps the prompt usable when no
|
|
2109
|
+
% template was set (e.g. a direct repl_meta_prompt/3 call).
|
|
2110
|
+
( session_meta_template(SessionId, Template)
|
|
2111
|
+
-> true
|
|
2112
|
+
; Template = "You are the DeepClause REPL harness author.~n~nHOST TOOL CATALOG~n{{CATALOG}}~n~nUSER REQUEST~n{{REQUEST}}~n"
|
|
2113
|
+
),
|
|
2114
|
+
replace_all(Template, "{{CATALOG}}", Catalog, P1),
|
|
2115
|
+
replace_all(P1, "{{REFERENCE}}", Reference, P2),
|
|
2116
|
+
replace_all(P2, "{{AUTHORING}}", Authoring, P3),
|
|
2117
|
+
replace_all(P3, "{{EXAMPLES}}", Examples, P4),
|
|
2118
|
+
replace_all(P4, "{{SNIPPETS}}", Snippets, P5),
|
|
2119
|
+
replace_all(P5, "{{MANIFEST}}", Manifest, P6),
|
|
2120
|
+
replace_all(P6, "{{MEMORY}}", MemoryText, P7),
|
|
2121
|
+
replace_all(P7, "{{REQUEST}}", Request, Prompt).
|
|
2122
|
+
|
|
2123
|
+
%% replace_all(+Text, +From, +To, -Out)
|
|
2124
|
+
%% Literal, all-occurrences substring replacement. Fills the meta-prompt
|
|
2125
|
+
%% template's {{PLACEHOLDER}} sections.
|
|
2126
|
+
replace_all(Text, From, To, Out) :-
|
|
2127
|
+
string_codes(Text, TextCodes),
|
|
2128
|
+
string_codes(From, FromCodes),
|
|
2129
|
+
string_codes(To, ToCodes),
|
|
2130
|
+
replace_all_codes(TextCodes, FromCodes, ToCodes, OutCodes),
|
|
2131
|
+
string_codes(Out, OutCodes).
|
|
2132
|
+
|
|
2133
|
+
replace_all_codes([], _, _, []) :- !.
|
|
2134
|
+
replace_all_codes(Codes, From, To, Out) :-
|
|
2135
|
+
From \== [],
|
|
2136
|
+
( append(From, Rest, Codes)
|
|
2137
|
+
-> append(To, Tail, Out),
|
|
2138
|
+
replace_all_codes(Rest, From, To, Tail)
|
|
2139
|
+
; Codes = [C|Cs],
|
|
2140
|
+
Out = [C|Tail],
|
|
2141
|
+
replace_all_codes(Cs, From, To, Tail)
|
|
2142
|
+
).
|
|
2143
|
+
|
|
2144
|
+
repl_meta_prompt(Request, Prompt) :-
|
|
2145
|
+
get_session_id(SessionId),
|
|
2146
|
+
repl_meta_prompt(SessionId, Request, Prompt).
|
|
2147
|
+
|
|
2148
|
+
%% repl_run_meta(+SessionId, +PromptText, -FrameId, -Error)
|
|
2149
|
+
%% Creates the meta frame running the code-writing task.
|
|
2150
|
+
repl_run_meta(SessionId, PromptText, FrameId, Error) :-
|
|
2151
|
+
catch(
|
|
2152
|
+
( repl_initial_state(SessionId, meta, State),
|
|
2153
|
+
create_frame_raw(SessionId, meta, 0,
|
|
2154
|
+
deepclause_mi:repl_meta_solve(PromptText, SessionId, State),
|
|
2155
|
+
FrameId),
|
|
2156
|
+
Error = none
|
|
2157
|
+
),
|
|
2158
|
+
CreateError,
|
|
2159
|
+
format(atom(Error), '~w', [CreateError])
|
|
2160
|
+
).
|
|
2161
|
+
|
|
2162
|
+
repl_meta_solve(PromptText, SessionId, State) :-
|
|
2163
|
+
nb_setval(current_session_id, SessionId),
|
|
2164
|
+
catch(
|
|
2165
|
+
( mi_call(
|
|
2166
|
+
task_named(PromptText, [DMLCode, Answer],
|
|
2167
|
+
[typed_var{name:'DMLCode', type:string},
|
|
2168
|
+
typed_var{name:'Answer', type:string}]),
|
|
2169
|
+
State, _StateOut),
|
|
2170
|
+
% The task's own memory (incl. the assembled prompt and tool results)
|
|
2171
|
+
% is ephemeral. The host records a clean user/assistant pair with
|
|
2172
|
+
% repl_record_meta/3 after the turn; that becomes the next turn's
|
|
2173
|
+
% conversation history (a normal messages array).
|
|
2174
|
+
engine_yield(meta_result(DMLCode, Answer))
|
|
2175
|
+
),
|
|
2176
|
+
MetaError,
|
|
2177
|
+
( format(atom(Msg), '~w', [MetaError]),
|
|
2178
|
+
engine_yield(error(Msg))
|
|
2179
|
+
)
|
|
2180
|
+
).
|
|
2181
|
+
|
|
2182
|
+
%% repl_record_meta(+SessionId, +Request, +Summary)
|
|
2183
|
+
%% Appends a short outcome note to meta memory (bounded).
|
|
2184
|
+
repl_record_meta(SessionId, Request, Summary) :-
|
|
2185
|
+
( session_meta_memory(SessionId, Memory)
|
|
2186
|
+
-> true
|
|
2187
|
+
; Memory = []
|
|
2188
|
+
),
|
|
2189
|
+
append(Memory,
|
|
2190
|
+
[message{role: user, content: Request},
|
|
2191
|
+
message{role: assistant, content: Summary}],
|
|
2192
|
+
Combined),
|
|
2193
|
+
% Keep every system message (compaction checkpoints) and the last 20
|
|
2194
|
+
% conversational messages; naive last_n/3 would evict old checkpoints.
|
|
2195
|
+
findall(M, (member(M, Combined), repl_system_message(M)), SystemMessages),
|
|
2196
|
+
findall(M, (member(M, Combined), \+ repl_system_message(M)), Conversational),
|
|
2197
|
+
last_n(Conversational, 20, Recent),
|
|
2198
|
+
append(SystemMessages, Recent, Bounded),
|
|
2199
|
+
retractall(session_meta_memory(SessionId, _)),
|
|
2200
|
+
assertz(session_meta_memory(SessionId, Bounded)).
|
|
2201
|
+
|
|
2202
|
+
%% repl_system_message(+Message)
|
|
2203
|
+
repl_system_message(message{role: system, content: _}).
|
|
2204
|
+
|
|
2205
|
+
%% repl_get_memory(+SessionId, +Slot, -Messages)
|
|
2206
|
+
%% Read a live memory slot as the message list stored in the session.
|
|
2207
|
+
repl_get_memory(SessionId, Slot, Messages) :-
|
|
2208
|
+
( Slot == meta
|
|
2209
|
+
-> ( session_meta_memory(SessionId, Memory)
|
|
2210
|
+
-> true
|
|
2211
|
+
; Memory = []
|
|
2212
|
+
)
|
|
2213
|
+
; ( session_repl_memory(SessionId, Memory)
|
|
2214
|
+
-> true
|
|
2215
|
+
; Memory = []
|
|
2216
|
+
)
|
|
2217
|
+
),
|
|
2218
|
+
Messages = Memory.
|
|
2219
|
+
|
|
2220
|
+
%% repl_set_memory(+SessionId, +Slot, +Messages)
|
|
2221
|
+
%% Replace a live memory slot (used by host-side TypeScript compaction).
|
|
2222
|
+
repl_set_memory(SessionId, Slot, Messages) :-
|
|
2223
|
+
( Slot == meta
|
|
2224
|
+
-> retractall(session_meta_memory(SessionId, _)),
|
|
2225
|
+
assertz(session_meta_memory(SessionId, Messages))
|
|
2226
|
+
; retractall(session_repl_memory(SessionId, _)),
|
|
2227
|
+
assertz(session_repl_memory(SessionId, Messages))
|
|
407
2228
|
).
|
|
408
2229
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
2230
|
+
%% ---- Edit transactions ----
|
|
2231
|
+
|
|
2232
|
+
%% repl_snapshot_save(+SessionId)
|
|
2233
|
+
%% Snapshots the living module, tool bookkeeping, directives, and harness
|
|
2234
|
+
%% memory so a failed prompt leaves the harness untouched.
|
|
2235
|
+
repl_snapshot_save(SessionId) :-
|
|
2236
|
+
retractall(repl_snapshot(SessionId, _)),
|
|
2237
|
+
findall(Clause,
|
|
2238
|
+
( current_predicate(SessionId:Name/Arity),
|
|
2239
|
+
repl_user_predicate(SessionId, Name, Arity),
|
|
2240
|
+
functor(Head, Name, Arity),
|
|
2241
|
+
clause(SessionId:Head, Body),
|
|
2242
|
+
( Body == true
|
|
2243
|
+
-> Clause = Head
|
|
2244
|
+
; Clause = (Head :- Body)
|
|
2245
|
+
)
|
|
2246
|
+
),
|
|
2247
|
+
Clauses),
|
|
2248
|
+
findall(Name, session_user_tools(SessionId, Name), Tools),
|
|
2249
|
+
findall(Name-Schema, session_user_tool_schema(SessionId, Name, Schema), Schemas),
|
|
2250
|
+
( current_predicate(SessionId:tool_source/2)
|
|
2251
|
+
-> findall(Name-Source, SessionId:tool_source(Name, Source), ToolSources)
|
|
2252
|
+
; ToolSources = []
|
|
2253
|
+
),
|
|
2254
|
+
findall(Directive, session_directive(SessionId, Directive), Directives),
|
|
2255
|
+
( session_repl_memory(SessionId, Memory) -> true ; Memory = [] ),
|
|
2256
|
+
assertz(repl_snapshot(SessionId, snapshot{
|
|
2257
|
+
clauses: Clauses,
|
|
2258
|
+
tools: Tools,
|
|
2259
|
+
schemas: Schemas,
|
|
2260
|
+
tool_sources: ToolSources,
|
|
2261
|
+
directives: Directives,
|
|
2262
|
+
memory: Memory
|
|
2263
|
+
})).
|
|
2264
|
+
|
|
2265
|
+
%% repl_snapshot_restore(+SessionId)
|
|
2266
|
+
repl_snapshot_restore(SessionId) :-
|
|
2267
|
+
( repl_snapshot(SessionId, Snapshot)
|
|
2268
|
+
-> retract(repl_snapshot(SessionId, Snapshot)),
|
|
2269
|
+
findall(Name/Arity,
|
|
2270
|
+
( current_predicate(SessionId:Name/Arity),
|
|
2271
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
2272
|
+
),
|
|
2273
|
+
Preds),
|
|
2274
|
+
forall(member(AbName/AbArity, Preds),
|
|
2275
|
+
catch(abolish(SessionId:AbName/AbArity), _, true)),
|
|
2276
|
+
forall(member(Clause, Snapshot.clauses), assertz(SessionId:Clause)),
|
|
2277
|
+
retractall(session_user_tools(SessionId, _)),
|
|
2278
|
+
forall(member(Tool, Snapshot.tools), assertz(session_user_tools(SessionId, Tool))),
|
|
2279
|
+
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
2280
|
+
forall(member(SchemaName-Schema, Snapshot.schemas),
|
|
2281
|
+
assertz(session_user_tool_schema(SessionId, SchemaName, Schema))),
|
|
2282
|
+
( current_predicate(SessionId:tool_source/2)
|
|
2283
|
+
-> retractall(SessionId:tool_source(_, _))
|
|
2284
|
+
; true
|
|
2285
|
+
),
|
|
2286
|
+
forall(member(SourceName-Source, Snapshot.tool_sources),
|
|
2287
|
+
assertz(SessionId:tool_source(SourceName, Source))),
|
|
2288
|
+
retractall(session_directive(SessionId, _)),
|
|
2289
|
+
forall(member(Directive, Snapshot.directives),
|
|
2290
|
+
assertz(session_directive(SessionId, Directive))),
|
|
2291
|
+
retractall(session_repl_memory(SessionId, _)),
|
|
2292
|
+
assertz(session_repl_memory(SessionId, Snapshot.memory))
|
|
2293
|
+
; true
|
|
2294
|
+
).
|
|
415
2295
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
2296
|
+
%% repl_parse_source(+SessionId, +Source, -Preds, -Error)
|
|
2297
|
+
%% Validates generated DML and collects the predicates it defines.
|
|
2298
|
+
repl_parse_source(SessionId, Source, Preds, Error) :-
|
|
2299
|
+
catch(
|
|
2300
|
+
( open_string(Source, Stream),
|
|
2301
|
+
repl_collect_source_predicates(Stream, SessionId, Preds0),
|
|
2302
|
+
close(Stream),
|
|
2303
|
+
( member(Name0/_, Preds0),
|
|
2304
|
+
repl_protected_name(Name0)
|
|
2305
|
+
-> format(atom(Error), 'generated code defines reserved predicate ~w', [Name0])
|
|
2306
|
+
; sort(Preds0, Preds),
|
|
2307
|
+
Error = none
|
|
2308
|
+
)
|
|
2309
|
+
),
|
|
2310
|
+
ParseError,
|
|
2311
|
+
format(atom(Error), '~w', [ParseError])
|
|
422
2312
|
).
|
|
423
2313
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
2314
|
+
%% repl_collect_source_predicates(+Stream, +SessionId, -Preds)
|
|
2315
|
+
%% Reads with the session's operator table and executes `:- use_module(...)`
|
|
2316
|
+
%% directives as it goes, exactly like the load path. This is what lets library
|
|
2317
|
+
%% operators (CLP(FD)'s #=/ins, etc.) be used in committed source: without it,
|
|
2318
|
+
%% validation parsed the source with an empty operator table and rejected code
|
|
2319
|
+
%% that loads fine.
|
|
2320
|
+
repl_collect_source_predicates(Stream, SessionId, Preds) :-
|
|
2321
|
+
read_term(Stream, Term, [module(SessionId)]),
|
|
2322
|
+
( Term == end_of_file
|
|
2323
|
+
-> Preds = []
|
|
2324
|
+
; ( Term = (:- Directive)
|
|
2325
|
+
-> catch(process_directive(Directive, SessionId), _, true),
|
|
2326
|
+
Preds = Rest
|
|
2327
|
+
; Term = repl_delete(_)
|
|
2328
|
+
-> Preds = Rest
|
|
2329
|
+
; Term = (Head :- _)
|
|
2330
|
+
-> functor(Head, Name, Arity),
|
|
2331
|
+
Preds = [Name/Arity|Rest]
|
|
2332
|
+
; functor(Term, Name, Arity),
|
|
2333
|
+
Preds = [Name/Arity|Rest]
|
|
2334
|
+
),
|
|
2335
|
+
repl_collect_source_predicates(Stream, SessionId, Rest)
|
|
428
2336
|
).
|
|
429
2337
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
2338
|
+
%% repl_commit(+SessionId, +Source, -Result)
|
|
2339
|
+
%% Transactional commit: validate, replace-by-predicate, then load the returned
|
|
2340
|
+
%% source. Result = ok | 'error(Message)'. Never throws; failures are reported
|
|
2341
|
+
%% through Result so the caller can decide whether to repair.
|
|
2342
|
+
repl_commit(SessionId, Source, Result) :-
|
|
2343
|
+
catch(
|
|
2344
|
+
repl_commit_unsafe(SessionId, Source, Result),
|
|
2345
|
+
CommitError,
|
|
2346
|
+
format(atom(Result), 'error(~w)', [CommitError])
|
|
434
2347
|
).
|
|
435
2348
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
process_directive(param(Key, Desc, Default), SessionId) :-
|
|
441
|
-
!,
|
|
442
|
-
assertz(SessionId:param_decl(Key, Desc, Default)).
|
|
2349
|
+
repl_commit_unsafe(SessionId, Source, Result) :-
|
|
2350
|
+
guard_not_frozen(SessionId),
|
|
2351
|
+
repl_parse_source(SessionId, Source, Preds, ParseError),
|
|
2352
|
+
repl_commit_from_parse(SessionId, Source, Preds, ParseError, Result).
|
|
443
2353
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
!,
|
|
447
|
-
% Set session ID for get_session_id to find, then call our custom consult
|
|
448
|
-
nb_setval(current_session_id, SessionId),
|
|
449
|
-
resolve_workspace_path(File, FullPath),
|
|
450
|
-
readutil:read_file_to_string(FullPath, Code, []),
|
|
451
|
-
consult_string(Code, SessionId).
|
|
452
|
-
%% Handle use_module directive
|
|
453
|
-
process_directive(use_module(Module), SessionId) :-
|
|
2354
|
+
repl_commit_from_parse(_SessionId, _Source, _Preds, ParseError, Result) :-
|
|
2355
|
+
ParseError \== none,
|
|
454
2356
|
!,
|
|
455
|
-
|
|
456
|
-
|
|
2357
|
+
commit_error(ParseError, Result).
|
|
2358
|
+
repl_commit_from_parse(SessionId, Source, Preds, none, Result) :-
|
|
2359
|
+
forall(member(Name/Arity, Preds),
|
|
2360
|
+
catch(abolish(SessionId:Name/Arity), _, true)),
|
|
2361
|
+
load_code(SessionId, Source, LoadError),
|
|
2362
|
+
( LoadError == none
|
|
2363
|
+
-> retractall(session_last_commit(SessionId, _)),
|
|
2364
|
+
assertz(session_last_commit(SessionId, Preds))
|
|
2365
|
+
; true
|
|
2366
|
+
),
|
|
2367
|
+
repl_commit_from_load(LoadError, Result).
|
|
2368
|
+
|
|
2369
|
+
%% repl_last_commit_summary(+SessionId, -Text)
|
|
2370
|
+
%% Rendered "name/arity, name/arity" of the predicates the last commit replaced.
|
|
2371
|
+
repl_last_commit_summary(SessionId, Text) :-
|
|
2372
|
+
( session_last_commit(SessionId, Preds), Preds \= []
|
|
2373
|
+
-> with_output_to(string(Text), print_pred_list(Preds))
|
|
2374
|
+
; Text = ""
|
|
2375
|
+
).
|
|
2376
|
+
|
|
2377
|
+
print_pred_list([]).
|
|
2378
|
+
print_pred_list([Name/Arity]) :-
|
|
457
2379
|
!,
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
2380
|
+
format("~w/~w", [Name, Arity]).
|
|
2381
|
+
print_pred_list([Name/Arity|Rest]) :-
|
|
2382
|
+
format("~w/~w, ", [Name, Arity]),
|
|
2383
|
+
print_pred_list(Rest).
|
|
2384
|
+
|
|
2385
|
+
repl_commit_from_load(LoadError, Result) :-
|
|
2386
|
+
LoadError \== none,
|
|
461
2387
|
!,
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
(Files == [] -> true ; process_directive(Files, SessionId)).
|
|
465
|
-
process_directive(_, _).
|
|
2388
|
+
commit_error(LoadError, Result).
|
|
2389
|
+
repl_commit_from_load(none, ok).
|
|
466
2390
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
%% ============================================================
|
|
2391
|
+
commit_error(Message, Result) :-
|
|
2392
|
+
format(atom(Result), 'error(~w)', [Message]).
|
|
470
2393
|
|
|
471
|
-
%%
|
|
472
|
-
%%
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
2394
|
+
%% repl_execute_frame(+SessionId, +GoalText, -FrameId, -Error)
|
|
2395
|
+
%% Creates a child frame for the meta `execute` tool.
|
|
2396
|
+
repl_execute_frame(SessionId, GoalText, FrameId, Error) :-
|
|
2397
|
+
catch(
|
|
2398
|
+
( read_repl_goal(SessionId, GoalText, Goal, Names),
|
|
2399
|
+
( repl_reserved(Goal)
|
|
2400
|
+
-> throw(error(permission_error(execute, reserved_predicate, Goal),
|
|
2401
|
+
context(repl_execute_frame/4, 'cannot execute a reserved predicate')))
|
|
2402
|
+
; true
|
|
2403
|
+
),
|
|
2404
|
+
repl_initial_state(SessionId, nested, State),
|
|
2405
|
+
create_frame_raw(SessionId, nested, 1,
|
|
2406
|
+
deepclause_mi:repl_solve(Goal, Names, SessionId, State),
|
|
2407
|
+
FrameId),
|
|
2408
|
+
Error = none
|
|
2409
|
+
),
|
|
2410
|
+
ExecuteError,
|
|
2411
|
+
format(atom(Error), '~w', [ExecuteError])
|
|
2412
|
+
).
|
|
490
2413
|
|
|
491
2414
|
%% determine_agent_goal(+SessionId, +Args, -Goal)
|
|
492
2415
|
%% Args is a list of positional arguments - passed directly to agent_main
|
|
@@ -527,31 +2450,12 @@ determine_agent_goal(SessionId, Args, Goal) :-
|
|
|
527
2450
|
determine_agent_goal(SessionId, _Args, SessionId:agent_main).
|
|
528
2451
|
|
|
529
2452
|
%% step_engine(+SessionId, -Status, -Content, -Payload)
|
|
2453
|
+
%% Legacy single-engine entry point: resolves the session's default frame and
|
|
2454
|
+
%% delegates to step_frame/4. Kept as a thin wrapper so existing callers are
|
|
2455
|
+
%% unaffected by the frame refactor.
|
|
530
2456
|
step_engine(SessionId, Status, Content, Payload) :-
|
|
531
|
-
(
|
|
532
|
-
->
|
|
533
|
-
( engine_next(Engine, Result)
|
|
534
|
-
-> process_engine_result(Result, Status, Content, Payload)
|
|
535
|
-
; % Engine finished - return trace if enabled
|
|
536
|
-
Status = finished,
|
|
537
|
-
Content = '',
|
|
538
|
-
( session_trace_enabled(SessionId, true),
|
|
539
|
-
session_trace_log(SessionId, TraceLog)
|
|
540
|
-
-> Payload = payload{trace: TraceLog}
|
|
541
|
-
; Payload = none
|
|
542
|
-
)
|
|
543
|
-
),
|
|
544
|
-
Error,
|
|
545
|
-
( format(atom(ErrMsg), 'Engine error: ~w', [Error]),
|
|
546
|
-
Status = error,
|
|
547
|
-
Content = ErrMsg,
|
|
548
|
-
( session_trace_enabled(SessionId, true),
|
|
549
|
-
session_trace_log(SessionId, TraceLog)
|
|
550
|
-
-> Payload = payload{trace: TraceLog}
|
|
551
|
-
; Payload = none
|
|
552
|
-
)
|
|
553
|
-
)
|
|
554
|
-
)
|
|
2457
|
+
( session_default_frame(SessionId, FrameId)
|
|
2458
|
+
-> step_frame(FrameId, Status, Content, Payload)
|
|
555
2459
|
; Status = error,
|
|
556
2460
|
Content = 'No engine found for session',
|
|
557
2461
|
Payload = none
|
|
@@ -562,7 +2466,7 @@ process_engine_result(output(Text), output, Text, none) :- !.
|
|
|
562
2466
|
process_engine_result(log(Text), log, Text, none) :- !.
|
|
563
2467
|
process_engine_result(answer(Text), answer, Text, none) :- !.
|
|
564
2468
|
%% Note: Memory and tool scope are now passed in the payload for the agent loop
|
|
565
|
-
process_engine_result(request_agent_loop(Desc, Vars, Tools, Memory, ToolScope, ReasoningEffort, RecipeContext), request_agent_loop, '',
|
|
2469
|
+
process_engine_result(request_agent_loop(Desc, Vars, Tools, Memory, ToolScope, ReasoningEffort, RecipeContext), request_agent_loop, '',
|
|
566
2470
|
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: ToolScope, reasoningEffort: ReasoningEffort, recipeContext: RecipeContext}) :- !.
|
|
567
2471
|
%% Legacy 5-arg format (no reasoning/recipe)
|
|
568
2472
|
process_engine_result(request_agent_loop(Desc, Vars, Tools, Memory, ToolScope), request_agent_loop, '',
|
|
@@ -576,9 +2480,17 @@ process_engine_result(request_sample_token(Prompt, Allowed), request_sample_toke
|
|
|
576
2480
|
payload{prompt: Prompt, allowedTokens: Allowed}) :- !.
|
|
577
2481
|
process_engine_result(request_llm(Messages), request_llm, '',
|
|
578
2482
|
payload{messages: Messages}) :- !.
|
|
2483
|
+
process_engine_result(request_judgment(Payload), request_judgment, '', Payload) :- !.
|
|
579
2484
|
%% Tool result from inline tool execution
|
|
580
2485
|
process_engine_result(tool_result(Result), tool_result, '',
|
|
581
2486
|
payload{result: Result}) :- !.
|
|
2487
|
+
%% Goal-mode solution: bindings is a list of Name-RenderedText pairs.
|
|
2488
|
+
process_engine_result(solution(Bindings, Deterministic), solution, '',
|
|
2489
|
+
payload{bindings: Bindings, deterministic: Deterministic}) :- !.
|
|
2490
|
+
%% Prompt-mode meta task result: generated DML source and an optional direct
|
|
2491
|
+
%% answer for simple questions.
|
|
2492
|
+
process_engine_result(meta_result(Code, Answer), meta_result, '',
|
|
2493
|
+
payload{code: Code, answer: Answer}) :- !.
|
|
582
2494
|
process_engine_result(wait_input(Prompt), wait_input, Prompt, none) :- !.
|
|
583
2495
|
process_engine_result(error(Msg), error, Msg, none) :- !.
|
|
584
2496
|
process_engine_result(Other, error, Msg, none) :-
|
|
@@ -586,19 +2498,28 @@ process_engine_result(Other, error, Msg, none) :-
|
|
|
586
2498
|
|
|
587
2499
|
%% destroy_engine(+SessionId)
|
|
588
2500
|
destroy_engine(SessionId) :-
|
|
2501
|
+
% Destroy every frame belonging to this session (the default query frame and
|
|
2502
|
+
% any nested frames still alive). end_frame/1 also clears the default pointer.
|
|
2503
|
+
( findall(FrameId, session_frame(FrameId, SessionId, _, _), FrameIds)
|
|
2504
|
+
-> forall(member(FrameId, FrameIds), end_frame(FrameId))
|
|
2505
|
+
; true
|
|
2506
|
+
),
|
|
589
2507
|
( session_engine(SessionId, Engine)
|
|
590
2508
|
-> catch(engine_destroy(Engine), _, true)
|
|
591
2509
|
; true
|
|
592
2510
|
),
|
|
2511
|
+
retractall(session_default_frame(SessionId, _)),
|
|
593
2512
|
retractall(session_engine(SessionId, _)),
|
|
594
2513
|
retractall(session_params(SessionId, _)),
|
|
595
2514
|
retractall(session_user_tools(SessionId, _)),
|
|
596
2515
|
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
597
2516
|
retractall(session_pending_input(SessionId, _)),
|
|
2517
|
+
retractall(session_input_cancelled(SessionId)),
|
|
598
2518
|
retractall(session_agent_result(SessionId, _)),
|
|
599
2519
|
retractall(session_exec_result(SessionId, _)),
|
|
600
2520
|
retractall(session_sample_token_result(SessionId, _)),
|
|
601
2521
|
retractall(session_llm_result(SessionId, _)),
|
|
2522
|
+
retractall(session_judgment_result(SessionId, _)),
|
|
602
2523
|
retractall(session_trace_log(SessionId, _)),
|
|
603
2524
|
retractall(session_trace_enabled(SessionId, _)),
|
|
604
2525
|
( current_module(SessionId)
|
|
@@ -645,6 +2566,17 @@ post_sample_token_result(SessionId, Status, TokenOrError) :-
|
|
|
645
2566
|
assertz(session_sample_token_result(SessionId, result{status: Status, token: TokenOrError})),
|
|
646
2567
|
post_signal_to_engine(SessionId, sample_token_done).
|
|
647
2568
|
|
|
2569
|
+
%% post_judgment_result(+SessionId, +Status, +ValuesOrError)
|
|
2570
|
+
%% On success Values is the list of answer values in question order.
|
|
2571
|
+
post_judgment_result(SessionId, success, Values) :-
|
|
2572
|
+
retractall(session_judgment_result(SessionId, _)),
|
|
2573
|
+
assertz(session_judgment_result(SessionId, result{status: success, values: Values})),
|
|
2574
|
+
post_signal_to_engine(SessionId, judgment_done).
|
|
2575
|
+
post_judgment_result(SessionId, failure, Error) :-
|
|
2576
|
+
retractall(session_judgment_result(SessionId, _)),
|
|
2577
|
+
assertz(session_judgment_result(SessionId, result{status: failure, error: Error})),
|
|
2578
|
+
post_signal_to_engine(SessionId, judgment_done).
|
|
2579
|
+
|
|
648
2580
|
%% post_llm_result(+SessionId, +Status, +TextOrError)
|
|
649
2581
|
post_llm_result(SessionId, Status, TextOrError) :-
|
|
650
2582
|
retractall(session_llm_result(SessionId, _)),
|
|
@@ -656,6 +2588,13 @@ provide_input(SessionId, Input) :-
|
|
|
656
2588
|
assertz(session_pending_input(SessionId, Input)),
|
|
657
2589
|
post_signal_to_engine(SessionId, input_provided).
|
|
658
2590
|
|
|
2591
|
+
%% provide_input_cancelled(+SessionId)
|
|
2592
|
+
%% Non-interactive runs have no human. Signal the waiting input/2 to fail
|
|
2593
|
+
%% instead of blocking.
|
|
2594
|
+
provide_input_cancelled(SessionId) :-
|
|
2595
|
+
assertz(session_input_cancelled(SessionId)),
|
|
2596
|
+
post_signal_to_engine(SessionId, input_provided).
|
|
2597
|
+
|
|
659
2598
|
%% post_signal_to_engine(+SessionId, +Signal)
|
|
660
2599
|
%% Posts a signal via dynamic predicate (most reliable for WASM engines)
|
|
661
2600
|
%% Falls back to engine_post only if dynamic assert fails
|
|
@@ -1042,6 +2981,7 @@ process_tool_engine_result(request_sample_token(Prompt, Allowed), request_sample
|
|
|
1042
2981
|
payload{prompt: Prompt, allowedTokens: Allowed}) :- !.
|
|
1043
2982
|
process_tool_engine_result(request_llm(Messages), request_llm, '',
|
|
1044
2983
|
payload{messages: Messages}) :- !.
|
|
2984
|
+
process_tool_engine_result(request_judgment(Payload), request_judgment, '', Payload) :- !.
|
|
1045
2985
|
process_tool_engine_result(request_agent_loop(Desc, Vars, Tools, Memory, ToolScope, ReasoningEffort, RecipeContext), request_agent_loop, '',
|
|
1046
2986
|
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: ToolScope, reasoningEffort: ReasoningEffort, recipeContext: RecipeContext}) :- !.
|
|
1047
2987
|
%% Legacy 5-arg format (no reasoning/recipe)
|
|
@@ -1313,6 +3253,175 @@ wait_sample_token_signal(SessionId) :-
|
|
|
1313
3253
|
; wait_sample_token_signal(SessionId)
|
|
1314
3254
|
).
|
|
1315
3255
|
|
|
3256
|
+
%% ============================================================
|
|
3257
|
+
%% Semantic Judgment Predicates
|
|
3258
|
+
%% ============================================================
|
|
3259
|
+
%% judge/2 asks one batch of typed questions about an explicit state and binds
|
|
3260
|
+
%% the answers into output variables carried by the spec terms. The one-off
|
|
3261
|
+
%% predicates are sugar over the same request.
|
|
3262
|
+
%%
|
|
3263
|
+
%% judge(Message, [
|
|
3264
|
+
%% choose("Which team?", [billing, orders, account]) - Team,
|
|
3265
|
+
%% rate("How frustrated?", [calm, frustrated, angry]) - Frustration,
|
|
3266
|
+
%% verify("Does it ask for a refund?") - Refund
|
|
3267
|
+
%% ]).
|
|
3268
|
+
%%
|
|
3269
|
+
%% Backends (llm, jev, mock) are selected with with_judgment/2, the workspace
|
|
3270
|
+
%% configuration, or a per-run override. require_judgment/2 gates a goal on a
|
|
3271
|
+
%% backend capability such as calibrated.
|
|
3272
|
+
|
|
3273
|
+
mi_call(judge(State, Specs), StateIn, StateOut) :-
|
|
3274
|
+
!,
|
|
3275
|
+
mi_call_judgment(State, Specs, StateIn, StateOut).
|
|
3276
|
+
|
|
3277
|
+
mi_call(choose(State, Question, Options, Choice), StateIn, StateOut) :-
|
|
3278
|
+
!,
|
|
3279
|
+
mi_call_judgment(State, [choose(Question, Options) - Choice], StateIn, StateOut).
|
|
3280
|
+
|
|
3281
|
+
mi_call(rate(State, Question, Levels, Level), StateIn, StateOut) :-
|
|
3282
|
+
!,
|
|
3283
|
+
mi_call_judgment(State, [rate(Question, Levels) - Level], StateIn, StateOut).
|
|
3284
|
+
|
|
3285
|
+
mi_call(verify(State, Question, Truth), StateIn, StateOut) :-
|
|
3286
|
+
!,
|
|
3287
|
+
mi_call_judgment(State, [verify(Question) - Truth], StateIn, StateOut).
|
|
3288
|
+
|
|
3289
|
+
mi_call(probability(State, Question, Probability), StateIn, StateOut) :-
|
|
3290
|
+
!,
|
|
3291
|
+
mi_call_judgment(State, [probability(Question) - Probability], StateIn, StateOut).
|
|
3292
|
+
|
|
3293
|
+
mi_call(holds(State, Question), StateIn, StateOut) :-
|
|
3294
|
+
!,
|
|
3295
|
+
mi_call_judgment(State, [verify(Question) - Truth], StateIn, StateOut),
|
|
3296
|
+
( Truth == yes
|
|
3297
|
+
-> true
|
|
3298
|
+
; fail
|
|
3299
|
+
).
|
|
3300
|
+
|
|
3301
|
+
mi_call(holds(State, Question, Threshold), StateIn, StateOut) :-
|
|
3302
|
+
!,
|
|
3303
|
+
mi_call_judgment(State, [probability(Question) - Probability], StateIn, StateOut),
|
|
3304
|
+
( number(Probability), Probability >= Threshold
|
|
3305
|
+
-> true
|
|
3306
|
+
; fail
|
|
3307
|
+
).
|
|
3308
|
+
|
|
3309
|
+
mi_call(with_judgment(Backend, Goal), StateIn, StateOut) :-
|
|
3310
|
+
!,
|
|
3311
|
+
set_judge_backend(StateIn, Backend, ScopedState),
|
|
3312
|
+
mi_call(Goal, ScopedState, StateWithScope),
|
|
3313
|
+
clear_judge_backend(StateWithScope, StateOut).
|
|
3314
|
+
|
|
3315
|
+
mi_call(require_judgment(Capability, Goal), StateIn, StateOut) :-
|
|
3316
|
+
!,
|
|
3317
|
+
push_required_judgment(StateIn, Capability, ScopedState),
|
|
3318
|
+
mi_call(Goal, ScopedState, StateWithScope),
|
|
3319
|
+
pop_required_judgment(StateWithScope, StateOut).
|
|
3320
|
+
|
|
3321
|
+
%% mi_call_judgment(+State, +Specs, +StateIn, -StateOut)
|
|
3322
|
+
mi_call_judgment(State, Specs, StateIn, StateOut) :-
|
|
3323
|
+
consume_gas(StateIn, State1),
|
|
3324
|
+
get_session_id(SessionId),
|
|
3325
|
+
get_depth(State1, Depth),
|
|
3326
|
+
split_specs(Specs, Questions, Outputs),
|
|
3327
|
+
length(Questions, Count),
|
|
3328
|
+
add_trace_entry(SessionId, llm_call, judgment, [Count], Depth),
|
|
3329
|
+
get_judge_backend(State1, Backend),
|
|
3330
|
+
get_required_judgments(State1, Requires),
|
|
3331
|
+
engine_yield(request_judgment(payload{
|
|
3332
|
+
state: State,
|
|
3333
|
+
questions: Questions,
|
|
3334
|
+
backend: Backend,
|
|
3335
|
+
requires: Requires
|
|
3336
|
+
})),
|
|
3337
|
+
wait_judgment_signal(SessionId),
|
|
3338
|
+
( session_judgment_result(SessionId, Result)
|
|
3339
|
+
-> retract(session_judgment_result(SessionId, _)),
|
|
3340
|
+
( Result.status == success
|
|
3341
|
+
-> add_trace_with_result(SessionId, exit, judgment, [Count], Result.values, Depth),
|
|
3342
|
+
bind_judgment_outputs(Outputs, Result.values),
|
|
3343
|
+
StateOut = State1
|
|
3344
|
+
; ( get_dict(error, Result, ErrorMsg), ErrorMsg \= ""
|
|
3345
|
+
-> format(atom(WarnMsg), 'Warning: judgment failed: ~w', [ErrorMsg])
|
|
3346
|
+
; format(atom(WarnMsg), 'Warning: judgment failed (status=~w)', [Result.status])
|
|
3347
|
+
),
|
|
3348
|
+
add_trace_entry(SessionId, fail, judgment, [Count], Depth),
|
|
3349
|
+
engine_yield(output(WarnMsg)),
|
|
3350
|
+
fail
|
|
3351
|
+
)
|
|
3352
|
+
; add_trace_entry(SessionId, fail, judgment, [Count], Depth),
|
|
3353
|
+
engine_yield(output('Warning: judgment failed (no result returned)')),
|
|
3354
|
+
fail
|
|
3355
|
+
).
|
|
3356
|
+
|
|
3357
|
+
wait_judgment_signal(SessionId) :-
|
|
3358
|
+
get_next_signal(SessionId, Signal),
|
|
3359
|
+
( Signal == judgment_done
|
|
3360
|
+
-> true
|
|
3361
|
+
; Signal = error_signal(_)
|
|
3362
|
+
-> true
|
|
3363
|
+
; wait_judgment_signal(SessionId)
|
|
3364
|
+
).
|
|
3365
|
+
|
|
3366
|
+
%% split_specs(+Specs, -Questions, -Outputs)
|
|
3367
|
+
%% Each spec is `Question - Output`. Outputs are bound after the batch.
|
|
3368
|
+
split_specs([], [], []).
|
|
3369
|
+
split_specs([Spec-Output|Rest], [Question|Questions], [Output|Outputs]) :-
|
|
3370
|
+
spec_question(Spec, Question),
|
|
3371
|
+
split_specs(Rest, Questions, Outputs).
|
|
3372
|
+
|
|
3373
|
+
spec_question(choose(Instruction, Options),
|
|
3374
|
+
question{kind: choose, instruction: Instruction, options: Options}).
|
|
3375
|
+
spec_question(rate(Instruction, Levels),
|
|
3376
|
+
question{kind: rate, instruction: Instruction, levels: Levels}).
|
|
3377
|
+
spec_question(verify(Instruction),
|
|
3378
|
+
question{kind: verify, instruction: Instruction}).
|
|
3379
|
+
spec_question(verify(Instruction, Criteria),
|
|
3380
|
+
question{kind: verify, instruction: Instruction, criteria: Criteria}).
|
|
3381
|
+
spec_question(probability(Instruction),
|
|
3382
|
+
question{kind: probability, instruction: Instruction}).
|
|
3383
|
+
|
|
3384
|
+
bind_judgment_outputs([], []).
|
|
3385
|
+
bind_judgment_outputs([Output|Outputs], [Value|Values]) :-
|
|
3386
|
+
Output = Value,
|
|
3387
|
+
bind_judgment_outputs(Outputs, Values).
|
|
3388
|
+
|
|
3389
|
+
%% get_judge_backend(+State, -Backend)
|
|
3390
|
+
get_judge_backend(State, Backend) :-
|
|
3391
|
+
( get_dict(judge_backend, State, Scoped)
|
|
3392
|
+
-> Backend = Scoped
|
|
3393
|
+
; Backend = default
|
|
3394
|
+
).
|
|
3395
|
+
|
|
3396
|
+
set_judge_backend(StateIn, Backend, StateOut) :-
|
|
3397
|
+
StateOut = StateIn.put(judge_backend, Backend).
|
|
3398
|
+
|
|
3399
|
+
clear_judge_backend(StateIn, StateOut) :-
|
|
3400
|
+
( get_dict(judge_backend, StateIn, _)
|
|
3401
|
+
-> del_dict(judge_backend, StateIn, _, StateOut)
|
|
3402
|
+
; StateOut = StateIn
|
|
3403
|
+
).
|
|
3404
|
+
|
|
3405
|
+
get_required_judgments(State, Requires) :-
|
|
3406
|
+
( get_dict(judgment_requires, State, Requires)
|
|
3407
|
+
-> true
|
|
3408
|
+
; Requires = []
|
|
3409
|
+
).
|
|
3410
|
+
|
|
3411
|
+
push_required_judgment(StateIn, Capability, StateOut) :-
|
|
3412
|
+
get_required_judgments(StateIn, Existing),
|
|
3413
|
+
( member(Capability, Existing)
|
|
3414
|
+
-> StateOut = StateIn
|
|
3415
|
+
; append(Existing, [Capability], Updated),
|
|
3416
|
+
StateOut = StateIn.put(judgment_requires, Updated)
|
|
3417
|
+
).
|
|
3418
|
+
|
|
3419
|
+
pop_required_judgment(StateIn, StateOut) :-
|
|
3420
|
+
( get_dict(judgment_requires, StateIn, _)
|
|
3421
|
+
-> del_dict(judgment_requires, StateIn, _, StateOut)
|
|
3422
|
+
; StateOut = StateIn
|
|
3423
|
+
).
|
|
3424
|
+
|
|
1316
3425
|
mi_call(llm(Messages, Reply), StateIn, StateOut) :-
|
|
1317
3426
|
!,
|
|
1318
3427
|
consume_gas(StateIn, State1),
|
|
@@ -1512,6 +3621,49 @@ mi_call(get_memory(Messages), StateIn, StateIn) :-
|
|
|
1512
3621
|
!,
|
|
1513
3622
|
get_memory(StateIn, Messages).
|
|
1514
3623
|
|
|
3624
|
+
%% ============================================================
|
|
3625
|
+
%% Media (see docs/DML_MEDIA.md)
|
|
3626
|
+
%% ============================================================
|
|
3627
|
+
|
|
3628
|
+
%% mi_call(read_media(+Path, -Media), +StateIn, -StateOut)
|
|
3629
|
+
%% Binds Media = media(Path, Mime) for a workspace-relative image. The bytes
|
|
3630
|
+
%% stay on disk; only the reference enters memory.
|
|
3631
|
+
mi_call(read_media(Path, Media), StateIn, StateIn) :-
|
|
3632
|
+
!,
|
|
3633
|
+
get_params(StateIn, Params),
|
|
3634
|
+
interpolate_desc(Path, Params, P),
|
|
3635
|
+
media_mime(P, Mime),
|
|
3636
|
+
Media = media(P, Mime).
|
|
3637
|
+
|
|
3638
|
+
%% mi_call(attach_media(+Media), +StateIn, -StateOut)
|
|
3639
|
+
%% Appends a user memory message carrying a reference-only media entry, so the
|
|
3640
|
+
%% next task/N can see the image.
|
|
3641
|
+
mi_call(attach_media(Media), StateIn, StateOut) :-
|
|
3642
|
+
!,
|
|
3643
|
+
media_dict(Media, media{path: Path, mime: Mime}),
|
|
3644
|
+
format(atom(Note), '[attached ~w: ~w]', [Mime, Path]),
|
|
3645
|
+
Message = message{role: user, content: Note, media: [media{path: Path, mime: Mime}]},
|
|
3646
|
+
get_memory(StateIn, Memory),
|
|
3647
|
+
append(Memory, [Message], NewMemory),
|
|
3648
|
+
set_memory(StateIn, NewMemory, StateOut).
|
|
3649
|
+
|
|
3650
|
+
%% media_dict(+Media, -Dict)
|
|
3651
|
+
%% Accepts media(Path, Mime) or a bare path; normalizes to the storage dict.
|
|
3652
|
+
media_dict(media(Path, Mime), media{path: Path, mime: Mime}) :- !.
|
|
3653
|
+
media_dict(Path, media{path: Path, mime: Mime}) :- media_mime(Path, Mime).
|
|
3654
|
+
|
|
3655
|
+
%% media_mime(+Path, -Mime)
|
|
3656
|
+
%% Allowed media types, matching pi image content blocks.
|
|
3657
|
+
media_mime(Path, Mime) :-
|
|
3658
|
+
( string(Path) -> atom_string(Atom, Path) ; Atom = Path ),
|
|
3659
|
+
( sub_atom(Atom, _, _, 0, '.png') -> Mime = "image/png"
|
|
3660
|
+
; sub_atom(Atom, _, _, 0, '.jpg') -> Mime = "image/jpeg"
|
|
3661
|
+
; sub_atom(Atom, _, _, 0, '.jpeg') -> Mime = "image/jpeg"
|
|
3662
|
+
; sub_atom(Atom, _, _, 0, '.gif') -> Mime = "image/gif"
|
|
3663
|
+
; sub_atom(Atom, _, _, 0, '.webp') -> Mime = "image/webp"
|
|
3664
|
+
; fail
|
|
3665
|
+
).
|
|
3666
|
+
|
|
1515
3667
|
%% ============================================================
|
|
1516
3668
|
%% Output Predicates
|
|
1517
3669
|
%% ============================================================
|
|
@@ -1544,8 +3696,11 @@ mi_call(input(Prompt, Input), StateIn, StateIn) :-
|
|
|
1544
3696
|
engine_yield(wait_input(InterpPrompt)),
|
|
1545
3697
|
% Use get_next_signal helper which handles both dynamic predicate and engine_fetch
|
|
1546
3698
|
get_next_signal(SessionId, _Signal),
|
|
1547
|
-
%
|
|
1548
|
-
(
|
|
3699
|
+
% A cancelled input request fails the goal (non-interactive policy).
|
|
3700
|
+
( session_input_cancelled(SessionId)
|
|
3701
|
+
-> retractall(session_input_cancelled(SessionId)),
|
|
3702
|
+
fail
|
|
3703
|
+
; session_pending_input(SessionId, Input)
|
|
1549
3704
|
-> retract(session_pending_input(SessionId, Input)),
|
|
1550
3705
|
add_trace_with_result(SessionId, exit, input, [InterpPrompt], Input, Depth)
|
|
1551
3706
|
; Input = "", % Default to empty if no input provided
|
|
@@ -1567,6 +3722,27 @@ mi_call(log(Text), StateIn, StateIn) :-
|
|
|
1567
3722
|
interpolate_desc(Text, Params, InterpText),
|
|
1568
3723
|
engine_yield(log(InterpText)).
|
|
1569
3724
|
|
|
3725
|
+
%% mi_call(format(Format, Args), +StateIn, -StateOut)
|
|
3726
|
+
%% In REPL mode, capture format/2 output as an output event rather than
|
|
3727
|
+
%% writing to the process stdout. Outside the REPL (runDML), keep normal
|
|
3728
|
+
%% behavior so existing traces/prompts are unaffected.
|
|
3729
|
+
mi_call(format(Format, Args), StateIn, StateIn) :-
|
|
3730
|
+
get_session_id(SessionId),
|
|
3731
|
+
( session_repl_mode(SessionId, true)
|
|
3732
|
+
-> with_output_to(string(Text), format(Format, Args)),
|
|
3733
|
+
mi_call(output(Text), StateIn, StateIn)
|
|
3734
|
+
; format(Format, Args)
|
|
3735
|
+
).
|
|
3736
|
+
|
|
3737
|
+
%% mi_call(writeln(Value), +StateIn, -StateOut)
|
|
3738
|
+
mi_call(writeln(Value), StateIn, StateIn) :-
|
|
3739
|
+
get_session_id(SessionId),
|
|
3740
|
+
( session_repl_mode(SessionId, true)
|
|
3741
|
+
-> with_output_to(string(Text), writeln(Value)),
|
|
3742
|
+
mi_call(output(Text), StateIn, StateIn)
|
|
3743
|
+
; writeln(Value)
|
|
3744
|
+
).
|
|
3745
|
+
|
|
1570
3746
|
%% ============================================================
|
|
1571
3747
|
%% Context Stack Management - for memory save/restore
|
|
1572
3748
|
%% ============================================================
|
|
@@ -1693,20 +3869,57 @@ mi_call(false, _StateIn, _StateOut) :-
|
|
|
1693
3869
|
%% mi_call(retractall(Head), +StateIn, -StateOut)
|
|
1694
3870
|
%% Database predicates must operate on the session module so DML-side facts
|
|
1695
3871
|
%% (draft/4, claim/3, ...) are asserted/retracted in the right module.
|
|
3872
|
+
%% repl_reserved_clause(+Term)
|
|
3873
|
+
%% True when Term defines a reserved predicate. directives and the legitimate
|
|
3874
|
+
%% repl_delete harness form are exempt.
|
|
3875
|
+
repl_reserved_clause((:- _)) :-
|
|
3876
|
+
!,
|
|
3877
|
+
fail.
|
|
3878
|
+
repl_reserved_clause(repl_delete(_)) :-
|
|
3879
|
+
!,
|
|
3880
|
+
fail.
|
|
3881
|
+
repl_reserved_clause((tool(ToolHead, _) :- _)) :-
|
|
3882
|
+
!,
|
|
3883
|
+
functor(ToolHead, Name, _),
|
|
3884
|
+
repl_reserved_name(Name).
|
|
3885
|
+
repl_reserved_clause((tool(ToolHead) :- _)) :-
|
|
3886
|
+
!,
|
|
3887
|
+
functor(ToolHead, Name, _),
|
|
3888
|
+
repl_reserved_name(Name).
|
|
3889
|
+
repl_reserved_clause((Head :- _)) :-
|
|
3890
|
+
functor(Head, Name, _),
|
|
3891
|
+
repl_protected_name(Name).
|
|
3892
|
+
repl_reserved_clause(Head) :-
|
|
3893
|
+
functor(Head, Name, _),
|
|
3894
|
+
repl_protected_name(Name).
|
|
3895
|
+
|
|
1696
3896
|
mi_call(assertz(Head), StateIn, StateIn) :-
|
|
1697
3897
|
get_session_id(SessionId),
|
|
3898
|
+
guard_not_frozen(SessionId),
|
|
3899
|
+
guard_reserved_clause(Head),
|
|
1698
3900
|
assertz(SessionId:Head).
|
|
1699
3901
|
|
|
1700
3902
|
mi_call(asserta(Head), StateIn, StateIn) :-
|
|
1701
3903
|
get_session_id(SessionId),
|
|
3904
|
+
guard_not_frozen(SessionId),
|
|
3905
|
+
guard_reserved_clause(Head),
|
|
1702
3906
|
asserta(SessionId:Head).
|
|
1703
3907
|
|
|
3908
|
+
guard_reserved_clause(Head) :-
|
|
3909
|
+
( repl_reserved_clause(Head)
|
|
3910
|
+
-> throw(error(permission_error(modify, reserved_name, Head),
|
|
3911
|
+
context(mi_call/3, 'cannot define a reserved predicate')))
|
|
3912
|
+
; true
|
|
3913
|
+
).
|
|
3914
|
+
|
|
1704
3915
|
mi_call(retract(Head), StateIn, StateIn) :-
|
|
1705
3916
|
get_session_id(SessionId),
|
|
3917
|
+
guard_not_frozen(SessionId),
|
|
1706
3918
|
retract(SessionId:Head).
|
|
1707
3919
|
|
|
1708
3920
|
mi_call(retractall(Head), StateIn, StateIn) :-
|
|
1709
3921
|
get_session_id(SessionId),
|
|
3922
|
+
guard_not_frozen(SessionId),
|
|
1710
3923
|
retractall(SessionId:Head).
|
|
1711
3924
|
|
|
1712
3925
|
%% ============================================================
|
|
@@ -1915,6 +4128,7 @@ resolve_workspace_path(RelPath, FullPath) :-
|
|
|
1915
4128
|
mi_call(consult(File), StateIn, StateIn) :-
|
|
1916
4129
|
!,
|
|
1917
4130
|
get_session_id(SessionId),
|
|
4131
|
+
guard_not_frozen(SessionId),
|
|
1918
4132
|
resolve_workspace_path(File, FullPath),
|
|
1919
4133
|
readutil:read_file_to_string(FullPath, Code, []),
|
|
1920
4134
|
consult_string(Code, SessionId).
|
|
@@ -1961,7 +4175,14 @@ consult_clauses(Stream, SessionId) :-
|
|
|
1961
4175
|
).
|
|
1962
4176
|
|
|
1963
4177
|
%% consult_process_term(+Term, +SessionId)
|
|
1964
|
-
%% Process a single term read during consult
|
|
4178
|
+
%% Process a single term read during consult. Reserved-name definitions are
|
|
4179
|
+
%% refused so consulted files cannot shadow the REPL control surface.
|
|
4180
|
+
consult_process_term(Term, _SessionId) :-
|
|
4181
|
+
repl_reserved_clause(Term),
|
|
4182
|
+
!,
|
|
4183
|
+
throw(error(permission_error(modify, reserved_name, Term),
|
|
4184
|
+
context(consult_process_term/2, 'reserved predicate'))).
|
|
4185
|
+
|
|
1965
4186
|
consult_process_term(:-(Directive), SessionId) :-
|
|
1966
4187
|
% Handle directives - route through mi_call so custom predicates like consult/1 work
|
|
1967
4188
|
!,
|
|
@@ -1980,7 +4201,7 @@ consult_process_term((tool(ToolHead, Description) :- Body), SessionId) :-
|
|
|
1980
4201
|
extract_tool_schema(ToolHead, Description, ToolName, Schema),
|
|
1981
4202
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
1982
4203
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
1983
|
-
|
|
4204
|
+
format_tool_source(ToolHead, Description, Body, SourceCode),
|
|
1984
4205
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
1985
4206
|
assertz(SessionId:(tool(ToolHead) :- Body)).
|
|
1986
4207
|
|
|
@@ -1990,7 +4211,7 @@ consult_process_term((tool(ToolHead) :- Body), SessionId) :-
|
|
|
1990
4211
|
extract_tool_schema(ToolHead, none, ToolName, Schema),
|
|
1991
4212
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
1992
4213
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
1993
|
-
|
|
4214
|
+
format_tool_source(ToolHead, none, Body, SourceCode),
|
|
1994
4215
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
1995
4216
|
assertz(SessionId:(tool(ToolHead) :- Body)).
|
|
1996
4217
|
|
|
@@ -2099,6 +4320,22 @@ mi_call(delete_directory(Dir), StateIn, StateIn) :-
|
|
|
2099
4320
|
resolve_workspace_path(Dir, FullPath),
|
|
2100
4321
|
delete_directory(FullPath).
|
|
2101
4322
|
|
|
4323
|
+
%% mi_call(size_file(File, Size), +StateIn, -StateOut)
|
|
4324
|
+
%% size_file/2 is a plain SWI builtin, so it would otherwise resolve `File`
|
|
4325
|
+
%% against the WASM process cwd (not the workspace) and demand `/workspace/...`.
|
|
4326
|
+
mi_call(size_file(File, Size), StateIn, StateIn) :-
|
|
4327
|
+
!,
|
|
4328
|
+
resolve_workspace_path(File, FullPath),
|
|
4329
|
+
size_file(FullPath, Size).
|
|
4330
|
+
|
|
4331
|
+
%% mi_call(time_file(File, Time), +StateIn, -StateOut)
|
|
4332
|
+
%% Modifications time (seconds since epoch); same workspace-relative rule as
|
|
4333
|
+
%% size_file/2.
|
|
4334
|
+
mi_call(time_file(File, Time), StateIn, StateIn) :-
|
|
4335
|
+
!,
|
|
4336
|
+
resolve_workspace_path(File, FullPath),
|
|
4337
|
+
time_file(FullPath, Time).
|
|
4338
|
+
|
|
2102
4339
|
%% ============================================================
|
|
2103
4340
|
%% Catch/Throw - STATE THREADED
|
|
2104
4341
|
%% ============================================================
|
|
@@ -2180,6 +4417,8 @@ is_mi_special_predicate(yield(_)).
|
|
|
2180
4417
|
is_mi_special_predicate(log(_)).
|
|
2181
4418
|
is_mi_special_predicate(system(_)).
|
|
2182
4419
|
is_mi_special_predicate(user(_)). %% Add user message to context
|
|
4420
|
+
is_mi_special_predicate(read_media(_,_)).
|
|
4421
|
+
is_mi_special_predicate(attach_media(_)).
|
|
2183
4422
|
is_mi_special_predicate(task(_)).
|
|
2184
4423
|
is_mi_special_predicate(task(_,_)).
|
|
2185
4424
|
is_mi_special_predicate(task(_,_,_)).
|
|
@@ -2205,8 +4444,28 @@ is_mi_special_predicate(without_tools(_,_)).
|
|
|
2205
4444
|
is_mi_special_predicate(with_reasoning(_,_)).
|
|
2206
4445
|
%% Recipe context scoping
|
|
2207
4446
|
is_mi_special_predicate(with_recipe(_,_)).
|
|
4447
|
+
%% Semantic judgment predicates
|
|
4448
|
+
is_mi_special_predicate(judge(_,_)).
|
|
4449
|
+
is_mi_special_predicate(choose(_,_,_,_)).
|
|
4450
|
+
is_mi_special_predicate(rate(_,_,_,_)).
|
|
4451
|
+
is_mi_special_predicate(verify(_,_,_)).
|
|
4452
|
+
is_mi_special_predicate(probability(_,_,_)).
|
|
4453
|
+
is_mi_special_predicate(holds(_,_)).
|
|
4454
|
+
is_mi_special_predicate(holds(_,_,_)).
|
|
4455
|
+
is_mi_special_predicate(with_judgment(_,_)).
|
|
4456
|
+
is_mi_special_predicate(require_judgment(_,_)).
|
|
2208
4457
|
%% Context stack management
|
|
2209
4458
|
is_mi_special_predicate(get_memory(_)).
|
|
4459
|
+
is_mi_special_predicate(format(_,_)).
|
|
4460
|
+
is_mi_special_predicate(writeln(_)).
|
|
4461
|
+
is_mi_special_predicate(input(_,_)).
|
|
4462
|
+
is_mi_special_predicate(call(_)).
|
|
4463
|
+
is_mi_special_predicate(call(_,_)).
|
|
4464
|
+
is_mi_special_predicate(call(_,_,_)).
|
|
4465
|
+
is_mi_special_predicate(call(_,_,_,_)).
|
|
4466
|
+
is_mi_special_predicate(once(_)).
|
|
4467
|
+
is_mi_special_predicate(ignore(_)).
|
|
4468
|
+
is_mi_special_predicate(not(_)).
|
|
2210
4469
|
is_mi_special_predicate(push_context).
|
|
2211
4470
|
is_mi_special_predicate(push_context(_)).
|
|
2212
4471
|
is_mi_special_predicate(pop_context).
|
|
@@ -2264,6 +4523,8 @@ is_mi_special_predicate(close(_)).
|
|
|
2264
4523
|
is_mi_special_predicate(read_string(_,_,_)).
|
|
2265
4524
|
is_mi_special_predicate(write(_,_)).
|
|
2266
4525
|
is_mi_special_predicate(exists_file(_)).
|
|
4526
|
+
is_mi_special_predicate(size_file(_,_)).
|
|
4527
|
+
is_mi_special_predicate(time_file(_,_)).
|
|
2267
4528
|
is_mi_special_predicate(exists_directory(_)).
|
|
2268
4529
|
is_mi_special_predicate(directory_files(_,_)).
|
|
2269
4530
|
is_mi_special_predicate(make_directory(_)).
|
|
@@ -2277,6 +4538,105 @@ is_mi_special_predicate(use_module(_,_)).
|
|
|
2277
4538
|
is_mi_special_predicate([_]).
|
|
2278
4539
|
is_mi_special_predicate([_|_]).
|
|
2279
4540
|
|
|
4541
|
+
%% mi_call(call(Goal), +StateIn, -StateOut)
|
|
4542
|
+
%% Route call/1 through the meta-interpreter so backtracking and the
|
|
4543
|
+
%% reserved-predicate guard apply (a bare call/repl_* would bypass them).
|
|
4544
|
+
mi_call(call(Goal), StateIn, StateOut) :-
|
|
4545
|
+
guard_reserved_goal(Goal, StateIn),
|
|
4546
|
+
!,
|
|
4547
|
+
mi_call(Goal, StateIn, StateOut).
|
|
4548
|
+
|
|
4549
|
+
%% mi_call(call(Closure, Extra...), +StateIn, -StateOut)
|
|
4550
|
+
%% call/2..4 must also route through the meta-interpreter; otherwise a wrapped
|
|
4551
|
+
%% task/prompt/exec runs in raw Prolog, where task_named/3 does not exist.
|
|
4552
|
+
mi_call(call(Goal, A), StateIn, StateOut) :-
|
|
4553
|
+
!,
|
|
4554
|
+
extend_callable(Goal, [A], Callable),
|
|
4555
|
+
mi_call(call(Callable), StateIn, StateOut).
|
|
4556
|
+
|
|
4557
|
+
mi_call(call(Goal, A, B), StateIn, StateOut) :-
|
|
4558
|
+
!,
|
|
4559
|
+
extend_callable(Goal, [A, B], Callable),
|
|
4560
|
+
mi_call(call(Callable), StateIn, StateOut).
|
|
4561
|
+
|
|
4562
|
+
mi_call(call(Goal, A, B, C), StateIn, StateOut) :-
|
|
4563
|
+
!,
|
|
4564
|
+
extend_callable(Goal, [A, B, C], Callable),
|
|
4565
|
+
mi_call(call(Callable), StateIn, StateOut).
|
|
4566
|
+
|
|
4567
|
+
%% extend_callable(+Closure, +ExtraArgs, -Callable)
|
|
4568
|
+
extend_callable(Closure, Extra, Callable) :-
|
|
4569
|
+
Closure =.. List,
|
|
4570
|
+
append(List, Extra, Extended),
|
|
4571
|
+
Callable =.. Extended.
|
|
4572
|
+
|
|
4573
|
+
%% mi_call(once(Goal), +StateIn, -StateOut)
|
|
4574
|
+
%% once/1 must route through the meta-interpreter. As a plain builtin it would
|
|
4575
|
+
%% run Goal in raw Prolog, so a wrapped task/prompt/exec fails with
|
|
4576
|
+
%% task_named/3 undefined. Commits to the first solution of Goal, like once/1.
|
|
4577
|
+
mi_call(once(Goal), StateIn, StateOut) :-
|
|
4578
|
+
!,
|
|
4579
|
+
once(mi_call(Goal, StateIn, StateOut)).
|
|
4580
|
+
|
|
4581
|
+
%% mi_call(ignore(Goal), +StateIn, -StateOut)
|
|
4582
|
+
mi_call(ignore(Goal), StateIn, StateOut) :-
|
|
4583
|
+
!,
|
|
4584
|
+
( once(mi_call(Goal, StateIn, StateOut0))
|
|
4585
|
+
-> StateOut = StateOut0
|
|
4586
|
+
; StateOut = StateIn
|
|
4587
|
+
).
|
|
4588
|
+
|
|
4589
|
+
%% mi_call(not(Goal), +StateIn, -StateOut) (alias for \+/1)
|
|
4590
|
+
mi_call(not(Goal), StateIn, StateIn) :-
|
|
4591
|
+
!,
|
|
4592
|
+
mi_call(\+(Goal), StateIn, StateIn).
|
|
4593
|
+
|
|
4594
|
+
%% Refuse reserved predicates invoked directly in non-meta frames. This is the
|
|
4595
|
+
%% second of the three-place reserved guard (execute, mi_call, assert/load).
|
|
4596
|
+
mi_call(Goal, StateIn, _StateOut) :-
|
|
4597
|
+
guard_reserved_direct(Goal, StateIn),
|
|
4598
|
+
!,
|
|
4599
|
+
throw(error(permission_error(execute, reserved_predicate, Goal),
|
|
4600
|
+
context(mi_call/3, 'cannot invoke a reserved REPL predicate'))).
|
|
4601
|
+
|
|
4602
|
+
%% guard_reserved_goal(+Goal, +StateIn)
|
|
4603
|
+
%% Throws when Goal names a reserved predicate and the frame is not meta.
|
|
4604
|
+
guard_reserved_goal(Goal, StateIn) :-
|
|
4605
|
+
( is_dict(StateIn), get_dict(frame_kind, StateIn, meta)
|
|
4606
|
+
-> true
|
|
4607
|
+
; nonvar(Goal),
|
|
4608
|
+
callable(Goal),
|
|
4609
|
+
functor(Goal, Name, _),
|
|
4610
|
+
repl_reserved_name(Name)
|
|
4611
|
+
-> throw(error(permission_error(execute, reserved_predicate, Name),
|
|
4612
|
+
context(mi_call/3, 'cannot invoke a reserved REPL predicate')))
|
|
4613
|
+
; true
|
|
4614
|
+
).
|
|
4615
|
+
|
|
4616
|
+
guard_reserved_direct(Goal, StateIn) :-
|
|
4617
|
+
( is_dict(StateIn), get_dict(frame_kind, StateIn, meta)
|
|
4618
|
+
-> fail
|
|
4619
|
+
; nonvar(Goal),
|
|
4620
|
+
callable(Goal),
|
|
4621
|
+
functor(Goal, Name, _),
|
|
4622
|
+
repl_reserved_name(Name)
|
|
4623
|
+
).
|
|
4624
|
+
|
|
4625
|
+
%% Untransformed task/N and prompt/N (N >= 2) are source-level sugar rewritten
|
|
4626
|
+
%% to task_named/3 / prompt_named/3 by transform_task_calls/3 at load time.
|
|
4627
|
+
%% There is no runtime handler for the untransformed form, so it would match no
|
|
4628
|
+
%% clause and fail silently (looking like an inexplicable "0 solutions"). Fail
|
|
4629
|
+
%% loudly instead: reaching here means a clause was asserted/run without the
|
|
4630
|
+
%% load-time preprocessing (assert/assert_many now apply it, so this is a guard).
|
|
4631
|
+
mi_call(Goal, _StateIn, _StateOut) :-
|
|
4632
|
+
compound(Goal),
|
|
4633
|
+
functor(Goal, Name, Arity),
|
|
4634
|
+
Arity >= 2,
|
|
4635
|
+
( Name == task ; Name == prompt ),
|
|
4636
|
+
!,
|
|
4637
|
+
throw(error(domain_error(unrewritten_task_form, Goal),
|
|
4638
|
+
context(mi_call/3, 'task/N and prompt/N are rewritten at load time (task_named/3, prompt_named/3); preprocess the clause before running it'))).
|
|
4639
|
+
|
|
2280
4640
|
%% mi_call(Goal, +StateIn, -StateOut)
|
|
2281
4641
|
%% Catch-all for built-in and user-defined predicates
|
|
2282
4642
|
%% MUST NOT match special predicates that have dedicated handlers
|
|
@@ -2331,9 +4691,14 @@ mi_call_dispatch(Goal, StateIn, StateOut) :-
|
|
|
2331
4691
|
clause(SessionId:Goal, Body),
|
|
2332
4692
|
NewDepth is Depth + 1,
|
|
2333
4693
|
set_depth(StateIn, NewDepth, State1),
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
4694
|
+
% Soft cut: execute the body for every solution (so findall/backtracking
|
|
4695
|
+
% works), but still record a fail trace when the body has none.
|
|
4696
|
+
( mi_call(Body, State1, State2)
|
|
4697
|
+
*-> add_trace_entry(SessionId, exit, Functor, Args, Depth),
|
|
4698
|
+
set_depth(State2, Depth, StateOut)
|
|
4699
|
+
; add_trace_entry(SessionId, fail, Functor, Args, Depth),
|
|
4700
|
+
fail
|
|
4701
|
+
)
|
|
2337
4702
|
).
|
|
2338
4703
|
|
|
2339
4704
|
mi_call_dispatch(Goal, StateIn, StateIn) :-
|
|
@@ -2489,6 +4854,17 @@ expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
|
2489
4854
|
rebuild_goal(Functor, StringArg, ExpandedRestArgs, ExpandedGoal)
|
|
2490
4855
|
).
|
|
2491
4856
|
|
|
4857
|
+
% Goals whose interpolated string is NOT the first argument (verify/3, judge/2,
|
|
4858
|
+
% choose/4, rate/4, probability/3, holds/2,3, and any user predicate): hoist the
|
|
4859
|
+
% interpolation into a preceding goal.
|
|
4860
|
+
expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
4861
|
+
compound(Goal),
|
|
4862
|
+
Goal =.. [Functor|Args],
|
|
4863
|
+
expand_args_hoist(Args, Bindings, PreGoals, ExpandedArgs, true),
|
|
4864
|
+
!,
|
|
4865
|
+
Bare =.. [Functor|ExpandedArgs],
|
|
4866
|
+
prepend_goals(PreGoals, Bare, ExpandedGoal).
|
|
4867
|
+
|
|
2492
4868
|
% Other goals - expand arguments
|
|
2493
4869
|
expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
2494
4870
|
compound(Goal), !,
|
|
@@ -2553,6 +4929,34 @@ expand_rest_args([A|As], Bindings, [EA|EAs]) :-
|
|
|
2553
4929
|
expand_body_interp(A, Bindings, EA),
|
|
2554
4930
|
expand_rest_args(As, Bindings, EAs).
|
|
2555
4931
|
|
|
4932
|
+
%% expand_args_hoist(+Args, +Bindings, -PreGoals, -Args2, -Hoisted)
|
|
4933
|
+
%% Expand each argument; any interpolated string becomes a goal in PreGoals and
|
|
4934
|
+
%% its argument becomes the temp variable that goal binds.
|
|
4935
|
+
expand_args_hoist([], _, [], [], false).
|
|
4936
|
+
expand_args_hoist([Arg|Args], Bindings, PreGoals, [Expanded|EArgs], Hoisted) :-
|
|
4937
|
+
expand_arg_hoist(Arg, Bindings, Pre, Expanded, ThisHoisted),
|
|
4938
|
+
expand_args_hoist(Args, Bindings, Rest, EArgs, RestHoisted),
|
|
4939
|
+
append(Pre, Rest, PreGoals),
|
|
4940
|
+
( ThisHoisted == true
|
|
4941
|
+
-> Hoisted = true
|
|
4942
|
+
; Hoisted = RestHoisted
|
|
4943
|
+
).
|
|
4944
|
+
|
|
4945
|
+
expand_arg_hoist(Arg, Bindings, [FormatGoal], TempVar, true) :-
|
|
4946
|
+
( string(Arg)
|
|
4947
|
+
; atom(Arg)
|
|
4948
|
+
),
|
|
4949
|
+
string_needs_interpolation(Arg),
|
|
4950
|
+
!,
|
|
4951
|
+
build_format_call(Arg, Bindings, TempVar, FormatGoal).
|
|
4952
|
+
expand_arg_hoist(Arg, _, [], Arg, false).
|
|
4953
|
+
|
|
4954
|
+
%% prepend_goals(+PreGoals, +Goal, -Expanded)
|
|
4955
|
+
prepend_goals([], Goal, Goal) :- !.
|
|
4956
|
+
prepend_goals(Goals, Goal, Expanded) :-
|
|
4957
|
+
list_to_conjunction(Goals, Conj),
|
|
4958
|
+
Expanded = (Conj, Goal).
|
|
4959
|
+
|
|
2556
4960
|
%% flatten_conjunction(+A, +B, -Conj)
|
|
2557
4961
|
%% Flatten nested conjunctions properly
|
|
2558
4962
|
flatten_conjunction((A1, A2), B, Result) :- !,
|