deepclause-sdk 0.0.89 → 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/README.md +417 -948
- package/dist/agent.d.ts +18 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +74 -21
- 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 +27 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/prolog/bridge.d.ts +3 -1
- package/dist/prolog/bridge.d.ts.map +1 -1
- package/dist/prolog/bridge.js +8 -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 +2245 -41
- 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 +132 -13
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +434 -277
- package/dist/runner.js.map +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +0 -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 +312 -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 +80 -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 +2245 -41
- 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/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,12 +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,
|
|
27
92
|
post_judgment_result/3,
|
|
28
93
|
provide_input/2,
|
|
94
|
+
provide_input_cancelled/1,
|
|
29
95
|
mi/3,
|
|
30
96
|
% Tool engine predicates for isolated tool execution
|
|
31
97
|
create_tool_engine/4,
|
|
@@ -42,7 +108,25 @@
|
|
|
42
108
|
:- discontiguous var_to_name/3.
|
|
43
109
|
|
|
44
110
|
%% Dynamic predicates for session state (engine management only, not memory!)
|
|
45
|
-
:- 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
|
|
46
130
|
:- dynamic session_params/2. % session_params(SessionId, ParamsDict)
|
|
47
131
|
:- dynamic session_user_tools/2. % session_user_tools(SessionId, ToolName)
|
|
48
132
|
:- dynamic session_user_tool_schema/3. % session_user_tool_schema(SessionId, ToolName, Schema)
|
|
@@ -56,6 +140,7 @@
|
|
|
56
140
|
:- dynamic session_trace_enabled/2. % session_trace_enabled(SessionId, true/false)
|
|
57
141
|
:- dynamic session_pending_signal/2. % session_pending_signal(SessionId, Signal) - fallback for engine_fetch
|
|
58
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)
|
|
59
144
|
|
|
60
145
|
%% ============================================================
|
|
61
146
|
%% DML Parsing
|
|
@@ -260,6 +345,182 @@ wrapper_type(list, array).
|
|
|
260
345
|
wrapper_type(object, object).
|
|
261
346
|
wrapper_type(dict, object).
|
|
262
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
|
+
|
|
263
524
|
%% var_to_name(+Bindings, +Var, -Name)
|
|
264
525
|
%% Look up a variable's name from the bindings list
|
|
265
526
|
var_to_name(Bindings, Var, Name) :-
|
|
@@ -269,7 +530,15 @@ var_to_name(Bindings, Var, Name) :-
|
|
|
269
530
|
).
|
|
270
531
|
|
|
271
532
|
%% process_clause(+Term, +SessionId)
|
|
272
|
-
%% 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
|
+
|
|
273
542
|
process_clause((:- Directive), SessionId) :-
|
|
274
543
|
!,
|
|
275
544
|
process_directive(Directive, SessionId).
|
|
@@ -281,8 +550,9 @@ process_clause((tool(ToolHead, Description) :- Body), SessionId) :-
|
|
|
281
550
|
extract_tool_schema(ToolHead, Description, ToolName, Schema),
|
|
282
551
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
283
552
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
284
|
-
% Store the source code for the tool description
|
|
285
|
-
|
|
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),
|
|
286
556
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
287
557
|
% Assert the tool implementation (use just ToolHead for execution)
|
|
288
558
|
assertz(SessionId:(tool(ToolHead) :- ExpandedBody)).
|
|
@@ -294,8 +564,8 @@ process_clause((tool(ToolHead) :- Body), SessionId) :-
|
|
|
294
564
|
extract_tool_schema(ToolHead, none, ToolName, Schema),
|
|
295
565
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
296
566
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
297
|
-
% Store the source code for the tool description
|
|
298
|
-
|
|
567
|
+
% Store the source code for the tool description (reverses task rewrites).
|
|
568
|
+
format_tool_source(ToolHead, none, Body, SourceCode),
|
|
299
569
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
300
570
|
% Assert the tool implementation
|
|
301
571
|
assertz(SessionId:(tool(ToolHead) :- ExpandedBody)).
|
|
@@ -309,6 +579,15 @@ process_clause((Head :- Body), SessionId) :-
|
|
|
309
579
|
),
|
|
310
580
|
assertz(SessionId:(Head :- ExpandedBody)).
|
|
311
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
|
+
|
|
312
591
|
process_clause(Fact, SessionId) :-
|
|
313
592
|
% Simple fact
|
|
314
593
|
assertz(SessionId:Fact).
|
|
@@ -426,15 +705,27 @@ workspace_module_reference(Module) :-
|
|
|
426
705
|
process_use_module_directive(Module, SessionId) :-
|
|
427
706
|
( workspace_module_reference(Module)
|
|
428
707
|
-> process_directive(consult(Module), SessionId)
|
|
429
|
-
; SessionId:use_module(Module)
|
|
708
|
+
; SessionId:use_module(Module),
|
|
709
|
+
import_library_into_mi(Module)
|
|
430
710
|
).
|
|
431
711
|
|
|
432
712
|
process_use_module_directive(Module, Exports, SessionId) :-
|
|
433
713
|
( workspace_module_reference(Module)
|
|
434
714
|
-> process_directive(consult(Module), SessionId)
|
|
435
|
-
; SessionId:use_module(Module, Exports)
|
|
715
|
+
; SessionId:use_module(Module, Exports),
|
|
716
|
+
import_library_into_mi(Module)
|
|
436
717
|
).
|
|
437
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
|
+
|
|
438
729
|
%% process_directive(+Directive, +SessionId)
|
|
439
730
|
process_directive(param(Key, Desc), SessionId) :-
|
|
440
731
|
!,
|
|
@@ -454,9 +745,17 @@ process_directive(consult(File), SessionId) :-
|
|
|
454
745
|
%% Handle use_module directive
|
|
455
746
|
process_directive(use_module(Module), SessionId) :-
|
|
456
747
|
!,
|
|
748
|
+
( workspace_module_reference(Module)
|
|
749
|
+
-> true
|
|
750
|
+
; record_directive(SessionId, use_module(Module))
|
|
751
|
+
),
|
|
457
752
|
process_use_module_directive(Module, SessionId).
|
|
458
753
|
process_directive(use_module(Module, Exports), SessionId) :-
|
|
459
754
|
!,
|
|
755
|
+
( workspace_module_reference(Module)
|
|
756
|
+
-> true
|
|
757
|
+
; record_directive(SessionId, use_module(Module, Exports))
|
|
758
|
+
),
|
|
460
759
|
process_use_module_directive(Module, Exports, SessionId).
|
|
461
760
|
%% Handle list-style consult: :- [File] or :- [File1, File2, ...]
|
|
462
761
|
process_directive([File|Files], SessionId) :-
|
|
@@ -473,6 +772,9 @@ process_directive(_, _).
|
|
|
473
772
|
%% create_engine(+SessionId, +MemoryId, +Args, +Params, +InitialMemory, -Engine)
|
|
474
773
|
%% Args is a list of positional arguments for agent_main
|
|
475
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.
|
|
476
778
|
create_engine(SessionId, _MemoryId, Args, Params, InitialMemory, Engine) :-
|
|
477
779
|
assertz(session_params(SessionId, Params)),
|
|
478
780
|
determine_agent_goal(SessionId, Args, Goal),
|
|
@@ -484,12 +786,1631 @@ create_engine(SessionId, _MemoryId, Args, Params, InitialMemory, Engine) :-
|
|
|
484
786
|
(get_dict(gas_limit, Params, GasLimit) -> true ; GasLimit = -1),
|
|
485
787
|
% Create initial state with provided memory, params, context stack, trace depth and gas
|
|
486
788
|
InitialState = state{memory: InitialMemory, params: Params, context_stack: [], depth: 0, gas_remaining: GasLimit},
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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, _)),
|
|
491
794
|
assertz(session_engine(SessionId, Engine)).
|
|
492
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))
|
|
2228
|
+
).
|
|
2229
|
+
|
|
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
|
+
).
|
|
2295
|
+
|
|
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])
|
|
2312
|
+
).
|
|
2313
|
+
|
|
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)
|
|
2336
|
+
).
|
|
2337
|
+
|
|
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])
|
|
2347
|
+
).
|
|
2348
|
+
|
|
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).
|
|
2353
|
+
|
|
2354
|
+
repl_commit_from_parse(_SessionId, _Source, _Preds, ParseError, Result) :-
|
|
2355
|
+
ParseError \== none,
|
|
2356
|
+
!,
|
|
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]) :-
|
|
2379
|
+
!,
|
|
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,
|
|
2387
|
+
!,
|
|
2388
|
+
commit_error(LoadError, Result).
|
|
2389
|
+
repl_commit_from_load(none, ok).
|
|
2390
|
+
|
|
2391
|
+
commit_error(Message, Result) :-
|
|
2392
|
+
format(atom(Result), 'error(~w)', [Message]).
|
|
2393
|
+
|
|
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
|
+
).
|
|
2413
|
+
|
|
493
2414
|
%% determine_agent_goal(+SessionId, +Args, -Goal)
|
|
494
2415
|
%% Args is a list of positional arguments - passed directly to agent_main
|
|
495
2416
|
%% Prefers arity that matches the number of arguments provided
|
|
@@ -529,31 +2450,12 @@ determine_agent_goal(SessionId, Args, Goal) :-
|
|
|
529
2450
|
determine_agent_goal(SessionId, _Args, SessionId:agent_main).
|
|
530
2451
|
|
|
531
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.
|
|
532
2456
|
step_engine(SessionId, Status, Content, Payload) :-
|
|
533
|
-
(
|
|
534
|
-
->
|
|
535
|
-
( engine_next(Engine, Result)
|
|
536
|
-
-> process_engine_result(Result, Status, Content, Payload)
|
|
537
|
-
; % Engine finished - return trace if enabled
|
|
538
|
-
Status = finished,
|
|
539
|
-
Content = '',
|
|
540
|
-
( session_trace_enabled(SessionId, true),
|
|
541
|
-
session_trace_log(SessionId, TraceLog)
|
|
542
|
-
-> Payload = payload{trace: TraceLog}
|
|
543
|
-
; Payload = none
|
|
544
|
-
)
|
|
545
|
-
),
|
|
546
|
-
Error,
|
|
547
|
-
( format(atom(ErrMsg), 'Engine error: ~w', [Error]),
|
|
548
|
-
Status = error,
|
|
549
|
-
Content = ErrMsg,
|
|
550
|
-
( session_trace_enabled(SessionId, true),
|
|
551
|
-
session_trace_log(SessionId, TraceLog)
|
|
552
|
-
-> Payload = payload{trace: TraceLog}
|
|
553
|
-
; Payload = none
|
|
554
|
-
)
|
|
555
|
-
)
|
|
556
|
-
)
|
|
2457
|
+
( session_default_frame(SessionId, FrameId)
|
|
2458
|
+
-> step_frame(FrameId, Status, Content, Payload)
|
|
557
2459
|
; Status = error,
|
|
558
2460
|
Content = 'No engine found for session',
|
|
559
2461
|
Payload = none
|
|
@@ -582,6 +2484,13 @@ process_engine_result(request_judgment(Payload), request_judgment, '', Payload)
|
|
|
582
2484
|
%% Tool result from inline tool execution
|
|
583
2485
|
process_engine_result(tool_result(Result), tool_result, '',
|
|
584
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}) :- !.
|
|
585
2494
|
process_engine_result(wait_input(Prompt), wait_input, Prompt, none) :- !.
|
|
586
2495
|
process_engine_result(error(Msg), error, Msg, none) :- !.
|
|
587
2496
|
process_engine_result(Other, error, Msg, none) :-
|
|
@@ -589,15 +2498,23 @@ process_engine_result(Other, error, Msg, none) :-
|
|
|
589
2498
|
|
|
590
2499
|
%% destroy_engine(+SessionId)
|
|
591
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
|
+
),
|
|
592
2507
|
( session_engine(SessionId, Engine)
|
|
593
2508
|
-> catch(engine_destroy(Engine), _, true)
|
|
594
2509
|
; true
|
|
595
2510
|
),
|
|
2511
|
+
retractall(session_default_frame(SessionId, _)),
|
|
596
2512
|
retractall(session_engine(SessionId, _)),
|
|
597
2513
|
retractall(session_params(SessionId, _)),
|
|
598
2514
|
retractall(session_user_tools(SessionId, _)),
|
|
599
2515
|
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
600
2516
|
retractall(session_pending_input(SessionId, _)),
|
|
2517
|
+
retractall(session_input_cancelled(SessionId)),
|
|
601
2518
|
retractall(session_agent_result(SessionId, _)),
|
|
602
2519
|
retractall(session_exec_result(SessionId, _)),
|
|
603
2520
|
retractall(session_sample_token_result(SessionId, _)),
|
|
@@ -671,6 +2588,13 @@ provide_input(SessionId, Input) :-
|
|
|
671
2588
|
assertz(session_pending_input(SessionId, Input)),
|
|
672
2589
|
post_signal_to_engine(SessionId, input_provided).
|
|
673
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
|
+
|
|
674
2598
|
%% post_signal_to_engine(+SessionId, +Signal)
|
|
675
2599
|
%% Posts a signal via dynamic predicate (most reliable for WASM engines)
|
|
676
2600
|
%% Falls back to engine_post only if dynamic assert fails
|
|
@@ -1697,6 +3621,49 @@ mi_call(get_memory(Messages), StateIn, StateIn) :-
|
|
|
1697
3621
|
!,
|
|
1698
3622
|
get_memory(StateIn, Messages).
|
|
1699
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
|
+
|
|
1700
3667
|
%% ============================================================
|
|
1701
3668
|
%% Output Predicates
|
|
1702
3669
|
%% ============================================================
|
|
@@ -1729,8 +3696,11 @@ mi_call(input(Prompt, Input), StateIn, StateIn) :-
|
|
|
1729
3696
|
engine_yield(wait_input(InterpPrompt)),
|
|
1730
3697
|
% Use get_next_signal helper which handles both dynamic predicate and engine_fetch
|
|
1731
3698
|
get_next_signal(SessionId, _Signal),
|
|
1732
|
-
%
|
|
1733
|
-
(
|
|
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)
|
|
1734
3704
|
-> retract(session_pending_input(SessionId, Input)),
|
|
1735
3705
|
add_trace_with_result(SessionId, exit, input, [InterpPrompt], Input, Depth)
|
|
1736
3706
|
; Input = "", % Default to empty if no input provided
|
|
@@ -1752,6 +3722,27 @@ mi_call(log(Text), StateIn, StateIn) :-
|
|
|
1752
3722
|
interpolate_desc(Text, Params, InterpText),
|
|
1753
3723
|
engine_yield(log(InterpText)).
|
|
1754
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
|
+
|
|
1755
3746
|
%% ============================================================
|
|
1756
3747
|
%% Context Stack Management - for memory save/restore
|
|
1757
3748
|
%% ============================================================
|
|
@@ -1878,20 +3869,57 @@ mi_call(false, _StateIn, _StateOut) :-
|
|
|
1878
3869
|
%% mi_call(retractall(Head), +StateIn, -StateOut)
|
|
1879
3870
|
%% Database predicates must operate on the session module so DML-side facts
|
|
1880
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
|
+
|
|
1881
3896
|
mi_call(assertz(Head), StateIn, StateIn) :-
|
|
1882
3897
|
get_session_id(SessionId),
|
|
3898
|
+
guard_not_frozen(SessionId),
|
|
3899
|
+
guard_reserved_clause(Head),
|
|
1883
3900
|
assertz(SessionId:Head).
|
|
1884
3901
|
|
|
1885
3902
|
mi_call(asserta(Head), StateIn, StateIn) :-
|
|
1886
3903
|
get_session_id(SessionId),
|
|
3904
|
+
guard_not_frozen(SessionId),
|
|
3905
|
+
guard_reserved_clause(Head),
|
|
1887
3906
|
asserta(SessionId:Head).
|
|
1888
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
|
+
|
|
1889
3915
|
mi_call(retract(Head), StateIn, StateIn) :-
|
|
1890
3916
|
get_session_id(SessionId),
|
|
3917
|
+
guard_not_frozen(SessionId),
|
|
1891
3918
|
retract(SessionId:Head).
|
|
1892
3919
|
|
|
1893
3920
|
mi_call(retractall(Head), StateIn, StateIn) :-
|
|
1894
3921
|
get_session_id(SessionId),
|
|
3922
|
+
guard_not_frozen(SessionId),
|
|
1895
3923
|
retractall(SessionId:Head).
|
|
1896
3924
|
|
|
1897
3925
|
%% ============================================================
|
|
@@ -2100,6 +4128,7 @@ resolve_workspace_path(RelPath, FullPath) :-
|
|
|
2100
4128
|
mi_call(consult(File), StateIn, StateIn) :-
|
|
2101
4129
|
!,
|
|
2102
4130
|
get_session_id(SessionId),
|
|
4131
|
+
guard_not_frozen(SessionId),
|
|
2103
4132
|
resolve_workspace_path(File, FullPath),
|
|
2104
4133
|
readutil:read_file_to_string(FullPath, Code, []),
|
|
2105
4134
|
consult_string(Code, SessionId).
|
|
@@ -2146,7 +4175,14 @@ consult_clauses(Stream, SessionId) :-
|
|
|
2146
4175
|
).
|
|
2147
4176
|
|
|
2148
4177
|
%% consult_process_term(+Term, +SessionId)
|
|
2149
|
-
%% 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
|
+
|
|
2150
4186
|
consult_process_term(:-(Directive), SessionId) :-
|
|
2151
4187
|
% Handle directives - route through mi_call so custom predicates like consult/1 work
|
|
2152
4188
|
!,
|
|
@@ -2165,7 +4201,7 @@ consult_process_term((tool(ToolHead, Description) :- Body), SessionId) :-
|
|
|
2165
4201
|
extract_tool_schema(ToolHead, Description, ToolName, Schema),
|
|
2166
4202
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
2167
4203
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
2168
|
-
|
|
4204
|
+
format_tool_source(ToolHead, Description, Body, SourceCode),
|
|
2169
4205
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
2170
4206
|
assertz(SessionId:(tool(ToolHead) :- Body)).
|
|
2171
4207
|
|
|
@@ -2175,7 +4211,7 @@ consult_process_term((tool(ToolHead) :- Body), SessionId) :-
|
|
|
2175
4211
|
extract_tool_schema(ToolHead, none, ToolName, Schema),
|
|
2176
4212
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
2177
4213
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
2178
|
-
|
|
4214
|
+
format_tool_source(ToolHead, none, Body, SourceCode),
|
|
2179
4215
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
2180
4216
|
assertz(SessionId:(tool(ToolHead) :- Body)).
|
|
2181
4217
|
|
|
@@ -2284,6 +4320,22 @@ mi_call(delete_directory(Dir), StateIn, StateIn) :-
|
|
|
2284
4320
|
resolve_workspace_path(Dir, FullPath),
|
|
2285
4321
|
delete_directory(FullPath).
|
|
2286
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
|
+
|
|
2287
4339
|
%% ============================================================
|
|
2288
4340
|
%% Catch/Throw - STATE THREADED
|
|
2289
4341
|
%% ============================================================
|
|
@@ -2365,6 +4417,8 @@ is_mi_special_predicate(yield(_)).
|
|
|
2365
4417
|
is_mi_special_predicate(log(_)).
|
|
2366
4418
|
is_mi_special_predicate(system(_)).
|
|
2367
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(_)).
|
|
2368
4422
|
is_mi_special_predicate(task(_)).
|
|
2369
4423
|
is_mi_special_predicate(task(_,_)).
|
|
2370
4424
|
is_mi_special_predicate(task(_,_,_)).
|
|
@@ -2402,6 +4456,16 @@ is_mi_special_predicate(with_judgment(_,_)).
|
|
|
2402
4456
|
is_mi_special_predicate(require_judgment(_,_)).
|
|
2403
4457
|
%% Context stack management
|
|
2404
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(_)).
|
|
2405
4469
|
is_mi_special_predicate(push_context).
|
|
2406
4470
|
is_mi_special_predicate(push_context(_)).
|
|
2407
4471
|
is_mi_special_predicate(pop_context).
|
|
@@ -2459,6 +4523,8 @@ is_mi_special_predicate(close(_)).
|
|
|
2459
4523
|
is_mi_special_predicate(read_string(_,_,_)).
|
|
2460
4524
|
is_mi_special_predicate(write(_,_)).
|
|
2461
4525
|
is_mi_special_predicate(exists_file(_)).
|
|
4526
|
+
is_mi_special_predicate(size_file(_,_)).
|
|
4527
|
+
is_mi_special_predicate(time_file(_,_)).
|
|
2462
4528
|
is_mi_special_predicate(exists_directory(_)).
|
|
2463
4529
|
is_mi_special_predicate(directory_files(_,_)).
|
|
2464
4530
|
is_mi_special_predicate(make_directory(_)).
|
|
@@ -2472,6 +4538,105 @@ is_mi_special_predicate(use_module(_,_)).
|
|
|
2472
4538
|
is_mi_special_predicate([_]).
|
|
2473
4539
|
is_mi_special_predicate([_|_]).
|
|
2474
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
|
+
|
|
2475
4640
|
%% mi_call(Goal, +StateIn, -StateOut)
|
|
2476
4641
|
%% Catch-all for built-in and user-defined predicates
|
|
2477
4642
|
%% MUST NOT match special predicates that have dedicated handlers
|
|
@@ -2689,6 +4854,17 @@ expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
|
2689
4854
|
rebuild_goal(Functor, StringArg, ExpandedRestArgs, ExpandedGoal)
|
|
2690
4855
|
).
|
|
2691
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
|
+
|
|
2692
4868
|
% Other goals - expand arguments
|
|
2693
4869
|
expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
2694
4870
|
compound(Goal), !,
|
|
@@ -2753,6 +4929,34 @@ expand_rest_args([A|As], Bindings, [EA|EAs]) :-
|
|
|
2753
4929
|
expand_body_interp(A, Bindings, EA),
|
|
2754
4930
|
expand_rest_args(As, Bindings, EAs).
|
|
2755
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
|
+
|
|
2756
4960
|
%% flatten_conjunction(+A, +B, -Conj)
|
|
2757
4961
|
%% Flatten nested conjunctions properly
|
|
2758
4962
|
flatten_conjunction((A1, A2), B, Result) :- !,
|