deepclause-sdk 0.0.89 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +481 -941
- package/dist/agent.d.ts +19 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +75 -21
- package/dist/agent.js.map +1 -1
- package/dist/cli/config.d.ts +385 -178
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +161 -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 +85 -411
- 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 +216 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +1392 -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 +202 -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 +42 -0
- package/dist/cli/visualize.d.ts.map +1 -0
- package/dist/cli/visualize.js +150 -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 +5 -1
- package/dist/prolog/bridge.d.ts.map +1 -1
- package/dist/prolog/bridge.js +10 -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 +2440 -49
- package/dist/repl/agent.d.ts +90 -0
- package/dist/repl/agent.d.ts.map +1 -0
- package/dist/repl/agent.js +410 -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 +321 -0
- package/dist/repl/backend.d.ts.map +1 -0
- package/dist/repl/backend.js +415 -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 +99 -0
- package/dist/repl/harness-tools.d.ts.map +1 -0
- package/dist/repl/harness-tools.js +192 -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/imagegen.d.ts +29 -0
- package/dist/repl/imagegen.d.ts.map +1 -0
- package/dist/repl/imagegen.js +84 -0
- package/dist/repl/imagegen.js.map +1 -0
- package/dist/repl/index.d.ts +130 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/repl/index.js +112 -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/model-resolver.d.ts +59 -0
- package/dist/repl/model-resolver.d.ts.map +1 -0
- package/dist/repl/model-resolver.js +156 -0
- package/dist/repl/model-resolver.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 +187 -0
- package/dist/repl/render.d.ts.map +1 -0
- package/dist/repl/render.js +808 -0
- package/dist/repl/render.js.map +1 -0
- package/dist/repl/session.d.ts +424 -0
- package/dist/repl/session.d.ts.map +1 -0
- package/dist/repl/session.js +1473 -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 +41 -0
- package/dist/repl/tui/transcript.d.ts.map +1 -0
- package/dist/repl/tui/transcript.js +139 -0
- package/dist/repl/tui/transcript.js.map +1 -0
- package/dist/runner.d.ts +143 -13
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +532 -290
- 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 +1447 -0
- package/dist/system/assets/docs/DML_AUTHORING.md +195 -0
- package/dist/system/assets/docs/DML_REFERENCE.md +344 -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 +136 -119
- 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/ascii.d.ts +55 -0
- package/dist/visualize/ascii.d.ts.map +1 -0
- package/dist/visualize/ascii.js +242 -0
- package/dist/visualize/ascii.js.map +1 -0
- package/dist/visualize/display.d.ts +24 -0
- package/dist/visualize/display.d.ts.map +1 -0
- package/dist/visualize/display.js +29 -0
- package/dist/visualize/display.js.map +1 -0
- package/dist/visualize/graphics.d.ts +35 -0
- package/dist/visualize/graphics.d.ts.map +1 -0
- package/dist/visualize/graphics.js +96 -0
- package/dist/visualize/graphics.js.map +1 -0
- package/dist/visualize/image.d.ts +38 -0
- package/dist/visualize/image.d.ts.map +1 -0
- package/dist/visualize/image.js +95 -0
- package/dist/visualize/image.js.map +1 -0
- package/dist/visualize/index.d.ts +59 -0
- package/dist/visualize/index.d.ts.map +1 -0
- package/dist/visualize/index.js +198 -0
- package/dist/visualize/index.js.map +1 -0
- package/dist/visualize/raster.d.ts +23 -0
- package/dist/visualize/raster.d.ts.map +1 -0
- package/dist/visualize/raster.js +47 -0
- package/dist/visualize/raster.js.map +1 -0
- package/dist/visualize/svg.d.ts +39 -0
- package/dist/visualize/svg.d.ts.map +1 -0
- package/dist/visualize/svg.js +148 -0
- package/dist/visualize/svg.js.map +1 -0
- package/dist/visualize/transcode.d.ts +13 -0
- package/dist/visualize/transcode.d.ts.map +1 -0
- package/dist/visualize/transcode.js +40 -0
- package/dist/visualize/transcode.js.map +1 -0
- package/docs/images/repl.png +0 -0
- package/package.json +19 -15
- package/src/prolog-src/deepclause_analysis.pl +10 -2
- package/src/prolog-src/deepclause_mi.pl +2440 -49
- 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,79 @@
|
|
|
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_probe_nondeterminism/6,
|
|
75
|
+
repl_set_meta_catalog/2,
|
|
76
|
+
repl_set_meta_template/2,
|
|
77
|
+
repl_set_harness_examples/2,
|
|
78
|
+
repl_set_prolog_snippets/2,
|
|
79
|
+
repl_run_meta/4,
|
|
80
|
+
repl_snapshot_save/1,
|
|
81
|
+
repl_snapshot_restore/1,
|
|
82
|
+
repl_commit/3,
|
|
83
|
+
repl_last_commit_summary/2,
|
|
84
|
+
repl_execute_frame/4,
|
|
85
|
+
repl_record_meta/3,
|
|
86
|
+
repl_memory_term/3,
|
|
87
|
+
repl_get_memory/3,
|
|
88
|
+
repl_set_memory/3,
|
|
23
89
|
post_agent_result/4,
|
|
24
90
|
post_exec_result/3,
|
|
25
91
|
post_sample_token_result/3,
|
|
26
92
|
post_llm_result/3,
|
|
27
93
|
post_judgment_result/3,
|
|
28
94
|
provide_input/2,
|
|
95
|
+
provide_input_cancelled/1,
|
|
29
96
|
mi/3,
|
|
30
97
|
% Tool engine predicates for isolated tool execution
|
|
31
98
|
create_tool_engine/4,
|
|
@@ -42,7 +109,25 @@
|
|
|
42
109
|
:- discontiguous var_to_name/3.
|
|
43
110
|
|
|
44
111
|
%% Dynamic predicates for session state (engine management only, not memory!)
|
|
45
|
-
:- dynamic session_engine/2. % session_engine(SessionId, Engine)
|
|
112
|
+
:- dynamic session_engine/2. % session_engine(SessionId, Engine) - legacy default engine handle
|
|
113
|
+
:- dynamic session_frame/4. % session_frame(FrameId, SessionId, Kind, Depth)
|
|
114
|
+
:- dynamic session_frame_engine/2. % session_frame_engine(FrameId, Engine)
|
|
115
|
+
:- dynamic session_default_frame/2. % session_default_frame(SessionId, FrameId)
|
|
116
|
+
:- dynamic session_repl_memory/2. % session_repl_memory(SessionId, HarnessMemory) - object/entry memory
|
|
117
|
+
:- dynamic session_meta_memory/2. % session_meta_memory(SessionId, MetaMemory) - code-writing task memory
|
|
118
|
+
:- dynamic session_repl_mode/2. % session_repl_mode(SessionId, true/false) - gates format/writeln output shims
|
|
119
|
+
:- dynamic session_frozen/2. % session_frozen(SessionId, true/false) - read-only lock
|
|
120
|
+
:- dynamic session_locked/2. % session_locked(SessionId, true/false) - irreversibly frozen (--locked)
|
|
121
|
+
:- dynamic session_directive/2. % session_directive(SessionId, Directive) - recorded :- directives for export
|
|
122
|
+
:- dynamic session_meta_catalog/2. % session_meta_catalog(SessionId, CatalogText) - host tool catalog for the meta prompt
|
|
123
|
+
:- dynamic session_dml_reference/2. % session_dml_reference(SessionId, Text) - bundled DML reference for the meta prompt
|
|
124
|
+
:- dynamic session_authoring_examples/2. % session_authoring_examples(SessionId, Text) - curated authoring patterns
|
|
125
|
+
:- dynamic session_meta_template/2. % session_meta_template(SessionId, Template) - bundled META_PROMPT.md template
|
|
126
|
+
:- dynamic session_harness_examples/2. % session_harness_examples(SessionId, Text) - bundled HARNESS_EXAMPLES.md
|
|
127
|
+
:- dynamic session_prolog_snippets/2. % session_prolog_snippets(SessionId, Text) - bundled PROLOG_SNIPPETS.md
|
|
128
|
+
:- dynamic session_last_commit/2. % session_last_commit(SessionId, [Name/Arity]) - predicates the last commit replaced
|
|
129
|
+
:- dynamic session_edit_touched/2. % session_edit_touched(SessionId, Name/Arity) - agent-mode edit transactions
|
|
130
|
+
:- dynamic repl_snapshot/2. % repl_snapshot(SessionId, Snapshot) - transactional edit snapshot
|
|
46
131
|
:- dynamic session_params/2. % session_params(SessionId, ParamsDict)
|
|
47
132
|
:- dynamic session_user_tools/2. % session_user_tools(SessionId, ToolName)
|
|
48
133
|
:- dynamic session_user_tool_schema/3. % session_user_tool_schema(SessionId, ToolName, Schema)
|
|
@@ -56,6 +141,7 @@
|
|
|
56
141
|
:- dynamic session_trace_enabled/2. % session_trace_enabled(SessionId, true/false)
|
|
57
142
|
:- dynamic session_pending_signal/2. % session_pending_signal(SessionId, Signal) - fallback for engine_fetch
|
|
58
143
|
:- dynamic session_user_input/2. % session_user_input(SessionId, Input) - for ask_user tool
|
|
144
|
+
:- dynamic session_input_cancelled/1. % session_input_cancelled(SessionId) - input/2 should fail (non-interactive)
|
|
59
145
|
|
|
60
146
|
%% ============================================================
|
|
61
147
|
%% DML Parsing
|
|
@@ -251,6 +337,7 @@ extract_var_info(Bindings, Term, BareVar, Info) :-
|
|
|
251
337
|
).
|
|
252
338
|
|
|
253
339
|
%% wrapper_type(?Wrapper, ?Type)
|
|
340
|
+
wrapper_type(media, media).
|
|
254
341
|
wrapper_type(string, string).
|
|
255
342
|
wrapper_type(integer, integer).
|
|
256
343
|
wrapper_type(float, number).
|
|
@@ -260,6 +347,183 @@ wrapper_type(list, array).
|
|
|
260
347
|
wrapper_type(object, object).
|
|
261
348
|
wrapper_type(dict, object).
|
|
262
349
|
|
|
350
|
+
%% ============================================================
|
|
351
|
+
%% Source-facing view of user clauses
|
|
352
|
+
%%
|
|
353
|
+
%% At load time task/N and prompt/N are rewritten to task_named/3 and
|
|
354
|
+
%% prompt_named/3, and interpolated descriptions are hoisted into a preceding
|
|
355
|
+
%% format/3. Those internal forms must not leak into listing/export — the meta
|
|
356
|
+
%% task only knows task/N — so reverse them for display only.
|
|
357
|
+
%% ============================================================
|
|
358
|
+
|
|
359
|
+
%% clean_clause_body(+Body, -CleanBody)
|
|
360
|
+
clean_clause_body(Body, Clean) :-
|
|
361
|
+
conj_list(Body, Goals),
|
|
362
|
+
clean_goal_list(Goals, CleanGoals),
|
|
363
|
+
( CleanGoals = [Single]
|
|
364
|
+
-> Clean = Single
|
|
365
|
+
; list_to_conjunction(CleanGoals, Clean)
|
|
366
|
+
).
|
|
367
|
+
|
|
368
|
+
conj_list((A, B), List) :-
|
|
369
|
+
!,
|
|
370
|
+
conj_list(A, LA),
|
|
371
|
+
conj_list(B, LB),
|
|
372
|
+
append(LA, LB, List).
|
|
373
|
+
conj_list(Goal, [Goal]).
|
|
374
|
+
|
|
375
|
+
clean_goal_list([], []) :- !.
|
|
376
|
+
clean_goal_list([Goal], [Clean]) :-
|
|
377
|
+
!,
|
|
378
|
+
clean_atomic(Goal, Clean).
|
|
379
|
+
clean_goal_list([First, Second | Rest], [CleanFirst | CleanRest]) :-
|
|
380
|
+
( merge_interpolation(First, Second, CleanFirst)
|
|
381
|
+
-> clean_goal_list(Rest, CleanRest)
|
|
382
|
+
; clean_atomic(First, CleanFirst),
|
|
383
|
+
clean_goal_list([Second | Rest], CleanRest)
|
|
384
|
+
).
|
|
385
|
+
|
|
386
|
+
%% merge_interpolation(+FormatGoal, +TaskGoal, -Merged)
|
|
387
|
+
%% Fold `format(string(D), T, Args), task_named(D, ...)` back into
|
|
388
|
+
%% `task("…{Var}…", ...)`.
|
|
389
|
+
merge_interpolation(FormatGoal, TaskGoal, Merged) :-
|
|
390
|
+
FormatGoal = format(string(D), Template, Args),
|
|
391
|
+
( TaskGoal = task_named(D, Vars, Infos)
|
|
392
|
+
; TaskGoal = prompt_named(D, Vars, Infos)
|
|
393
|
+
),
|
|
394
|
+
task_functor(TaskGoal, Functor),
|
|
395
|
+
interpolate_template(Template, Args, Description),
|
|
396
|
+
clean_task(Functor, Description, Vars, Infos, Merged).
|
|
397
|
+
|
|
398
|
+
task_functor(task_named(_, _, _), task).
|
|
399
|
+
task_functor(prompt_named(_, _, _), prompt).
|
|
400
|
+
|
|
401
|
+
clean_atomic(task_named(D, Vars, Infos), Clean) :-
|
|
402
|
+
!,
|
|
403
|
+
clean_task(task, D, Vars, Infos, Clean).
|
|
404
|
+
clean_atomic(prompt_named(D, Vars, Infos), Clean) :-
|
|
405
|
+
!,
|
|
406
|
+
clean_task(prompt, D, Vars, Infos, Clean).
|
|
407
|
+
clean_atomic((A ; B), (CA ; CB)) :-
|
|
408
|
+
!,
|
|
409
|
+
clean_clause_body(A, CA),
|
|
410
|
+
clean_clause_body(B, CB).
|
|
411
|
+
clean_atomic((A -> B), (CA -> CB)) :-
|
|
412
|
+
!,
|
|
413
|
+
clean_clause_body(A, CA),
|
|
414
|
+
clean_clause_body(B, CB).
|
|
415
|
+
clean_atomic((A -> B ; C), (CA -> CB ; CC)) :-
|
|
416
|
+
!,
|
|
417
|
+
clean_clause_body(A, CA),
|
|
418
|
+
clean_clause_body(B, CB),
|
|
419
|
+
clean_clause_body(C, CC).
|
|
420
|
+
clean_atomic(\+(A), \+ CA) :-
|
|
421
|
+
!,
|
|
422
|
+
clean_clause_body(A, CA).
|
|
423
|
+
clean_atomic(not(A), not(CA)) :-
|
|
424
|
+
!,
|
|
425
|
+
clean_clause_body(A, CA).
|
|
426
|
+
% Wrappers: recurse into their goal arguments so a task/prompt inside is also
|
|
427
|
+
% reversed (the load-time rewrite descends into these via transform_task_calls).
|
|
428
|
+
clean_atomic(with_tools(Tools, Goal), with_tools(Tools, CleanGoal)) :-
|
|
429
|
+
!,
|
|
430
|
+
clean_clause_body(Goal, CleanGoal).
|
|
431
|
+
clean_atomic(without_tools(Tools, Goal), without_tools(Tools, CleanGoal)) :-
|
|
432
|
+
!,
|
|
433
|
+
clean_clause_body(Goal, CleanGoal).
|
|
434
|
+
clean_atomic(with_judgment(Backend, Goal), with_judgment(Backend, CleanGoal)) :-
|
|
435
|
+
!,
|
|
436
|
+
clean_clause_body(Goal, CleanGoal).
|
|
437
|
+
clean_atomic(require_judgment(Backend, Goal), require_judgment(Backend, CleanGoal)) :-
|
|
438
|
+
!,
|
|
439
|
+
clean_clause_body(Goal, CleanGoal).
|
|
440
|
+
clean_atomic(once(Goal), once(CleanGoal)) :-
|
|
441
|
+
!,
|
|
442
|
+
clean_clause_body(Goal, CleanGoal).
|
|
443
|
+
clean_atomic(ignore(Goal), ignore(CleanGoal)) :-
|
|
444
|
+
!,
|
|
445
|
+
clean_clause_body(Goal, CleanGoal).
|
|
446
|
+
clean_atomic(catch(Goal, Catcher, Recovery), catch(CleanGoal, Catcher, CleanRecovery)) :-
|
|
447
|
+
!,
|
|
448
|
+
clean_clause_body(Goal, CleanGoal),
|
|
449
|
+
clean_clause_body(Recovery, CleanRecovery).
|
|
450
|
+
clean_atomic(forall(Generator, Action), forall(CleanGenerator, CleanAction)) :-
|
|
451
|
+
!,
|
|
452
|
+
clean_clause_body(Generator, CleanGenerator),
|
|
453
|
+
clean_clause_body(Action, CleanAction).
|
|
454
|
+
clean_atomic(findall(Template, Goal, List), findall(Template, CleanGoal, List)) :-
|
|
455
|
+
!,
|
|
456
|
+
clean_clause_body(Goal, CleanGoal).
|
|
457
|
+
clean_atomic(Module:Goal, Module:CleanGoal) :-
|
|
458
|
+
!,
|
|
459
|
+
clean_clause_body(Goal, CleanGoal).
|
|
460
|
+
clean_atomic(Goal, Goal).
|
|
461
|
+
|
|
462
|
+
%% clean_task(+Functor, +Description, +Vars, +Infos, -Clean)
|
|
463
|
+
clean_task(Functor, Description, Vars, Infos, Clean) :-
|
|
464
|
+
clean_typed_args(Vars, Infos, CleanVars),
|
|
465
|
+
Clean =.. [Functor, Description | CleanVars].
|
|
466
|
+
|
|
467
|
+
clean_typed_args([], [], []) :- !.
|
|
468
|
+
clean_typed_args([Var | Vars], [Info | Infos], [Wrapped | Wrappeds]) :-
|
|
469
|
+
!,
|
|
470
|
+
typed_arg(Var, Info, Wrapped),
|
|
471
|
+
clean_typed_args(Vars, Infos, Wrappeds).
|
|
472
|
+
clean_typed_args(Vars, _, Vars).
|
|
473
|
+
|
|
474
|
+
%% typed_arg(+Var, +Info, -Wrapped) — reverse typed_var{...} to its wrapper.
|
|
475
|
+
typed_arg(Var, Info, Wrapped) :-
|
|
476
|
+
is_dict(Info),
|
|
477
|
+
get_dict(type, Info, Type),
|
|
478
|
+
!,
|
|
479
|
+
typed_arg_type(Type, Var, Info, Wrapped).
|
|
480
|
+
typed_arg(Var, _, Var).
|
|
481
|
+
|
|
482
|
+
typed_arg_type(media, Var, _, media(Var)) :- !.
|
|
483
|
+
typed_arg_type(string, Var, _, Var) :- !.
|
|
484
|
+
typed_arg_type(integer, Var, _, integer(Var)) :- !.
|
|
485
|
+
typed_arg_type(number, Var, _, number(Var)) :- !.
|
|
486
|
+
typed_arg_type(boolean, Var, _, boolean(Var)) :- !.
|
|
487
|
+
typed_arg_type(object, Var, _, object(Var)) :- !.
|
|
488
|
+
typed_arg_type(array, Var, Info, list(Wrapped)) :-
|
|
489
|
+
!,
|
|
490
|
+
( get_dict(itemType, Info, ItemType)
|
|
491
|
+
-> true
|
|
492
|
+
; ItemType = string
|
|
493
|
+
),
|
|
494
|
+
Wrapped =.. [ItemType, Var].
|
|
495
|
+
typed_arg_type(_, Var, _, Var).
|
|
496
|
+
|
|
497
|
+
%% interpolate_template(+Template, +Args, -Description)
|
|
498
|
+
%% Reverse of the {Var} -> ~w hoist. Succeeds only when every placeholder is a
|
|
499
|
+
%% plain variable, so the caller can fall back to keeping the format/3 goal.
|
|
500
|
+
interpolate_template(Template, Args, Description) :-
|
|
501
|
+
string(Template),
|
|
502
|
+
string_codes(Template, Codes),
|
|
503
|
+
interpolate_codes(Codes, Args, OutCodes),
|
|
504
|
+
string_codes(Description, OutCodes).
|
|
505
|
+
|
|
506
|
+
interpolate_codes([], [], []) :- !.
|
|
507
|
+
%% A {Var} placeholder consumes one argument; require Args to be exhausted
|
|
508
|
+
%% exactly (the []/[] base case above fails if either side has leftovers).
|
|
509
|
+
interpolate_codes([0'~, 0'w | Rest], [Arg | Args], Out) :-
|
|
510
|
+
!,
|
|
511
|
+
named_var(Arg),
|
|
512
|
+
term_string(Arg, Name, [numbervars(true)]),
|
|
513
|
+
string_codes(Name, NameCodes),
|
|
514
|
+
interpolate_codes(Rest, Args, Tail),
|
|
515
|
+
append([0'{|NameCodes], [0'}|Tail], Out).
|
|
516
|
+
interpolate_codes([0'~, 0'~ | Rest], Args, [0'~ | Out]) :-
|
|
517
|
+
!,
|
|
518
|
+
interpolate_codes(Rest, Args, Out).
|
|
519
|
+
interpolate_codes([Code | Rest], Args, [Code | Out]) :-
|
|
520
|
+
interpolate_codes(Rest, Args, Out).
|
|
521
|
+
|
|
522
|
+
%% An interpolation argument is a variable or a `$VAR(N)` term (after
|
|
523
|
+
%% numbervars), both of which print as a stable name (A, B, ...).
|
|
524
|
+
named_var(Arg) :- var(Arg), !.
|
|
525
|
+
named_var('$VAR'(_)).
|
|
526
|
+
|
|
263
527
|
%% var_to_name(+Bindings, +Var, -Name)
|
|
264
528
|
%% Look up a variable's name from the bindings list
|
|
265
529
|
var_to_name(Bindings, Var, Name) :-
|
|
@@ -269,7 +533,15 @@ var_to_name(Bindings, Var, Name) :-
|
|
|
269
533
|
).
|
|
270
534
|
|
|
271
535
|
%% process_clause(+Term, +SessionId)
|
|
272
|
-
%% Process a single clause and add to session
|
|
536
|
+
%% Process a single clause and add to session.
|
|
537
|
+
%% Reject reserved-name definitions (third place of the reserved guard, so
|
|
538
|
+
%% consulted/generated code cannot shadow the REPL control surface).
|
|
539
|
+
process_clause(Term, _SessionId) :-
|
|
540
|
+
repl_reserved_clause(Term),
|
|
541
|
+
!,
|
|
542
|
+
throw(error(permission_error(modify, reserved_name, Term),
|
|
543
|
+
context(process_clause/2, 'reserved predicate'))).
|
|
544
|
+
|
|
273
545
|
process_clause((:- Directive), SessionId) :-
|
|
274
546
|
!,
|
|
275
547
|
process_directive(Directive, SessionId).
|
|
@@ -281,8 +553,9 @@ process_clause((tool(ToolHead, Description) :- Body), SessionId) :-
|
|
|
281
553
|
extract_tool_schema(ToolHead, Description, ToolName, Schema),
|
|
282
554
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
283
555
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
284
|
-
% Store the source code for the tool description
|
|
285
|
-
|
|
556
|
+
% Store the source code for the tool description. Reverses the load-time
|
|
557
|
+
% task/prompt rewrites and normalizes variables for deterministic export.
|
|
558
|
+
format_tool_source(ToolHead, Description, Body, SourceCode),
|
|
286
559
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
287
560
|
% Assert the tool implementation (use just ToolHead for execution)
|
|
288
561
|
assertz(SessionId:(tool(ToolHead) :- ExpandedBody)).
|
|
@@ -294,8 +567,8 @@ process_clause((tool(ToolHead) :- Body), SessionId) :-
|
|
|
294
567
|
extract_tool_schema(ToolHead, none, ToolName, Schema),
|
|
295
568
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
296
569
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
297
|
-
% Store the source code for the tool description
|
|
298
|
-
|
|
570
|
+
% Store the source code for the tool description (reverses task rewrites).
|
|
571
|
+
format_tool_source(ToolHead, none, Body, SourceCode),
|
|
299
572
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
300
573
|
% Assert the tool implementation
|
|
301
574
|
assertz(SessionId:(tool(ToolHead) :- ExpandedBody)).
|
|
@@ -309,6 +582,15 @@ process_clause((Head :- Body), SessionId) :-
|
|
|
309
582
|
),
|
|
310
583
|
assertz(SessionId:(Head :- ExpandedBody)).
|
|
311
584
|
|
|
585
|
+
%% repl_delete/1 is a load-time directive that removes a predicate. It must
|
|
586
|
+
%% be handled before the generic fact clause so it is never asserted itself.
|
|
587
|
+
process_clause(repl_delete(Name/Arity), SessionId) :-
|
|
588
|
+
!,
|
|
589
|
+
( atom(Name), integer(Arity)
|
|
590
|
+
-> catch(abolish(SessionId:Name/Arity), _, true)
|
|
591
|
+
; true
|
|
592
|
+
).
|
|
593
|
+
|
|
312
594
|
process_clause(Fact, SessionId) :-
|
|
313
595
|
% Simple fact
|
|
314
596
|
assertz(SessionId:Fact).
|
|
@@ -426,15 +708,27 @@ workspace_module_reference(Module) :-
|
|
|
426
708
|
process_use_module_directive(Module, SessionId) :-
|
|
427
709
|
( workspace_module_reference(Module)
|
|
428
710
|
-> process_directive(consult(Module), SessionId)
|
|
429
|
-
; SessionId:use_module(Module)
|
|
711
|
+
; SessionId:use_module(Module),
|
|
712
|
+
import_library_into_mi(Module)
|
|
430
713
|
).
|
|
431
714
|
|
|
432
715
|
process_use_module_directive(Module, Exports, SessionId) :-
|
|
433
716
|
( workspace_module_reference(Module)
|
|
434
717
|
-> process_directive(consult(Module), SessionId)
|
|
435
|
-
; SessionId:use_module(Module, Exports)
|
|
718
|
+
; SessionId:use_module(Module, Exports),
|
|
719
|
+
import_library_into_mi(Module)
|
|
436
720
|
).
|
|
437
721
|
|
|
722
|
+
%% System libraries are also imported into the meta-interpreter module itself,
|
|
723
|
+
%% so meta-calls (`call_with_inference_limit/3`, `once/1`, `catch/3`, ...) can
|
|
724
|
+
%% resolve them. Without this, `call_with_inference_limit(label(Xs), ...)` fails
|
|
725
|
+
%% with `existence_error(deepclause_mi:label/1)` because clpfd was imported only
|
|
726
|
+
%% into the session module.
|
|
727
|
+
import_library_into_mi(library(Name)) :-
|
|
728
|
+
!,
|
|
729
|
+
catch(deepclause_mi:use_module(library(Name)), _, true).
|
|
730
|
+
import_library_into_mi(_).
|
|
731
|
+
|
|
438
732
|
%% process_directive(+Directive, +SessionId)
|
|
439
733
|
process_directive(param(Key, Desc), SessionId) :-
|
|
440
734
|
!,
|
|
@@ -454,9 +748,17 @@ process_directive(consult(File), SessionId) :-
|
|
|
454
748
|
%% Handle use_module directive
|
|
455
749
|
process_directive(use_module(Module), SessionId) :-
|
|
456
750
|
!,
|
|
751
|
+
( workspace_module_reference(Module)
|
|
752
|
+
-> true
|
|
753
|
+
; record_directive(SessionId, use_module(Module))
|
|
754
|
+
),
|
|
457
755
|
process_use_module_directive(Module, SessionId).
|
|
458
756
|
process_directive(use_module(Module, Exports), SessionId) :-
|
|
459
757
|
!,
|
|
758
|
+
( workspace_module_reference(Module)
|
|
759
|
+
-> true
|
|
760
|
+
; record_directive(SessionId, use_module(Module, Exports))
|
|
761
|
+
),
|
|
460
762
|
process_use_module_directive(Module, Exports, SessionId).
|
|
461
763
|
%% Handle list-style consult: :- [File] or :- [File1, File2, ...]
|
|
462
764
|
process_directive([File|Files], SessionId) :-
|
|
@@ -473,6 +775,9 @@ process_directive(_, _).
|
|
|
473
775
|
%% create_engine(+SessionId, +MemoryId, +Args, +Params, +InitialMemory, -Engine)
|
|
474
776
|
%% Args is a list of positional arguments for agent_main
|
|
475
777
|
%% Params is a dict of named parameters (already asserted as param/2 facts)
|
|
778
|
+
%%
|
|
779
|
+
%% The engine is registered as the session's default `query` frame so that
|
|
780
|
+
%% step_engine/4 keeps working as a thin wrapper over step_frame/4.
|
|
476
781
|
create_engine(SessionId, _MemoryId, Args, Params, InitialMemory, Engine) :-
|
|
477
782
|
assertz(session_params(SessionId, Params)),
|
|
478
783
|
determine_agent_goal(SessionId, Args, Goal),
|
|
@@ -484,12 +789,1712 @@ create_engine(SessionId, _MemoryId, Args, Params, InitialMemory, Engine) :-
|
|
|
484
789
|
(get_dict(gas_limit, Params, GasLimit) -> true ; GasLimit = -1),
|
|
485
790
|
% Create initial state with provided memory, params, context stack, trace depth and gas
|
|
486
791
|
InitialState = state{memory: InitialMemory, params: Params, context_stack: [], depth: 0, gas_remaining: GasLimit},
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
792
|
+
create_frame(SessionId, query, 0, Goal, InitialState, FrameId),
|
|
793
|
+
retractall(session_default_frame(SessionId, _)),
|
|
794
|
+
assertz(session_default_frame(SessionId, FrameId)),
|
|
795
|
+
session_frame_engine(FrameId, Engine),
|
|
796
|
+
retractall(session_engine(SessionId, _)),
|
|
491
797
|
assertz(session_engine(SessionId, Engine)).
|
|
492
798
|
|
|
799
|
+
%% ============================================================
|
|
800
|
+
%% Frames (re-entrant execution)
|
|
801
|
+
%% ============================================================
|
|
802
|
+
%%
|
|
803
|
+
%% Historically a session owned exactly one engine (session_engine/2).
|
|
804
|
+
%% Prompt mode requires nested engines: a meta frame that calls `execute`
|
|
805
|
+
%% which runs a child frame, which may itself call task() and spawn a
|
|
806
|
+
%% further frame. A frame is therefore (FrameId, SessionId, Kind, Depth)
|
|
807
|
+
%% with its engine stored in session_frame_engine/2.
|
|
808
|
+
%%
|
|
809
|
+
%% The legacy step_engine/4 resolves the session's default query frame and
|
|
810
|
+
%% delegates to step_frame/4, so single-engine callers are unaffected.
|
|
811
|
+
|
|
812
|
+
default_frame(SessionId, FrameId) :-
|
|
813
|
+
session_default_frame(SessionId, FrameId).
|
|
814
|
+
|
|
815
|
+
%% frame_info(+FrameId, -SessionId, -Kind, -Depth)
|
|
816
|
+
frame_info(FrameId, SessionId, Kind, Depth) :-
|
|
817
|
+
session_frame(FrameId, SessionId, Kind, Depth).
|
|
818
|
+
|
|
819
|
+
%% create_frame_raw(+SessionId, +Kind, +Depth, +EngineGoal, -FrameId)
|
|
820
|
+
%% Registers a frame whose engine runs EngineGoal verbatim. Most callers want
|
|
821
|
+
%% create_frame/6, which wraps the goal in the mi/3 meta-interpreter; goal mode
|
|
822
|
+
%% uses this to run repl_solve/4 directly (mi/3 cuts after the first solution).
|
|
823
|
+
create_frame_raw(SessionId, Kind, Depth, EngineGoal, FrameId) :-
|
|
824
|
+
gensym(frame_, FrameId),
|
|
825
|
+
engine_create(_, EngineGoal, Engine),
|
|
826
|
+
assertz(session_frame(FrameId, SessionId, Kind, Depth)),
|
|
827
|
+
assertz(session_frame_engine(FrameId, Engine)).
|
|
828
|
+
|
|
829
|
+
%% create_frame(+SessionId, +Kind, +Depth, +Goal, +InitialState, -FrameId)
|
|
830
|
+
%% Kind = query | meta | nested | object
|
|
831
|
+
%% Creates an engine running deepclause_mi:mi(Goal, InitialState, SessionId)
|
|
832
|
+
%% and registers it as a frame. The caller is responsible for depth-first
|
|
833
|
+
%% stepping: a parent frame is suspended while a child frame runs, so the
|
|
834
|
+
%% per-session result/signal slots are never concurrently pending.
|
|
835
|
+
create_frame(SessionId, Kind, Depth, Goal, InitialState, FrameId) :-
|
|
836
|
+
create_frame_raw(SessionId, Kind, Depth,
|
|
837
|
+
deepclause_mi:mi(Goal, InitialState, SessionId),
|
|
838
|
+
FrameId).
|
|
839
|
+
|
|
840
|
+
%% step_frame(+FrameId, -Status, -Content, -Payload)
|
|
841
|
+
step_frame(FrameId, Status, Content, Payload) :-
|
|
842
|
+
( session_frame(FrameId, SessionId, _Kind, _Depth),
|
|
843
|
+
session_frame_engine(FrameId, Engine)
|
|
844
|
+
-> step_engine_handle(SessionId, Engine, Status, Content, Payload)
|
|
845
|
+
; Status = error,
|
|
846
|
+
Content = 'No engine found for frame',
|
|
847
|
+
Payload = none
|
|
848
|
+
).
|
|
849
|
+
|
|
850
|
+
%% step_engine_handle(+SessionId, +Engine, -Status, -Content, -Payload)
|
|
851
|
+
%% Shared stepping logic for both step_frame/4 and the legacy step_engine/4.
|
|
852
|
+
step_engine_handle(SessionId, Engine, Status, Content, Payload) :-
|
|
853
|
+
catch(
|
|
854
|
+
( engine_next(Engine, Result)
|
|
855
|
+
-> process_engine_result(Result, Status, Content, Payload)
|
|
856
|
+
; % Engine finished - return trace if enabled
|
|
857
|
+
Status = finished,
|
|
858
|
+
Content = '',
|
|
859
|
+
( session_trace_enabled(SessionId, true),
|
|
860
|
+
session_trace_log(SessionId, TraceLog)
|
|
861
|
+
-> Payload = payload{trace: TraceLog}
|
|
862
|
+
; Payload = none
|
|
863
|
+
)
|
|
864
|
+
),
|
|
865
|
+
Error,
|
|
866
|
+
( format(atom(ErrMsg), 'Engine error: ~w', [Error]),
|
|
867
|
+
Status = error,
|
|
868
|
+
Content = ErrMsg,
|
|
869
|
+
( session_trace_enabled(SessionId, true),
|
|
870
|
+
session_trace_log(SessionId, TraceLog)
|
|
871
|
+
-> Payload = payload{trace: TraceLog}
|
|
872
|
+
; Payload = none
|
|
873
|
+
)
|
|
874
|
+
)
|
|
875
|
+
).
|
|
876
|
+
|
|
877
|
+
%% end_frame(+FrameId)
|
|
878
|
+
%% Destroys the frame's engine and unregisters the frame. Also clears the
|
|
879
|
+
%% session's default-frame pointer if this frame was the default.
|
|
880
|
+
end_frame(FrameId) :-
|
|
881
|
+
( session_frame_engine(FrameId, Engine)
|
|
882
|
+
-> catch(engine_destroy(Engine), _, true)
|
|
883
|
+
; true
|
|
884
|
+
),
|
|
885
|
+
( session_frame(FrameId, SessionId, _, _)
|
|
886
|
+
-> retractall(session_default_frame(SessionId, FrameId))
|
|
887
|
+
; true
|
|
888
|
+
),
|
|
889
|
+
retractall(session_frame_engine(FrameId, _)),
|
|
890
|
+
retractall(session_frame(FrameId, _, _, _)).
|
|
891
|
+
|
|
892
|
+
%% ============================================================
|
|
893
|
+
%% REPL Session and Goal Mode (M1)
|
|
894
|
+
%% ============================================================
|
|
895
|
+
%%
|
|
896
|
+
%% A REPL session owns a persistent living module. Unlike create_engine/6,
|
|
897
|
+
%% which is tied to a single run and destroyed by destroy_engine/1, a session
|
|
898
|
+
%% lives across inputs; reset/clear_session empties it without tearing down the
|
|
899
|
+
%% process-level SWI instance.
|
|
900
|
+
|
|
901
|
+
%% create_session(+SessionId, +Params, +HarnessMemory, +MetaMemory)
|
|
902
|
+
create_session(SessionId, Params, HarnessMemory, MetaMemory) :-
|
|
903
|
+
retractall(session_params(SessionId, _)),
|
|
904
|
+
assertz(session_params(SessionId, Params)),
|
|
905
|
+
( get_dict(trace, Params, true)
|
|
906
|
+
-> TraceEnabled = true
|
|
907
|
+
; TraceEnabled = false
|
|
908
|
+
),
|
|
909
|
+
retractall(session_trace_enabled(SessionId, _)),
|
|
910
|
+
assertz(session_trace_enabled(SessionId, TraceEnabled)),
|
|
911
|
+
retractall(session_trace_log(SessionId, _)),
|
|
912
|
+
assertz(session_trace_log(SessionId, [])),
|
|
913
|
+
retractall(session_repl_memory(SessionId, _)),
|
|
914
|
+
assertz(session_repl_memory(SessionId, HarnessMemory)),
|
|
915
|
+
retractall(session_meta_memory(SessionId, _)),
|
|
916
|
+
assertz(session_meta_memory(SessionId, MetaMemory)),
|
|
917
|
+
retractall(session_repl_mode(SessionId, _)),
|
|
918
|
+
assertz(session_repl_mode(SessionId, true)),
|
|
919
|
+
retractall(session_frozen(SessionId, _)),
|
|
920
|
+
assertz(session_frozen(SessionId, false)),
|
|
921
|
+
retractall(session_locked(SessionId, _)),
|
|
922
|
+
assertz(session_locked(SessionId, false)).
|
|
923
|
+
|
|
924
|
+
%% repl_abolish_user_predicates(+SessionId)
|
|
925
|
+
%% Removes only the harness's own predicates; imported builtins are untouched.
|
|
926
|
+
repl_abolish_user_predicates(SessionId) :-
|
|
927
|
+
( current_module(SessionId)
|
|
928
|
+
-> findall(Name/Arity,
|
|
929
|
+
( current_predicate(SessionId:Name/Arity),
|
|
930
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
931
|
+
),
|
|
932
|
+
UserPreds0),
|
|
933
|
+
sort(UserPreds0, UserPreds),
|
|
934
|
+
forall(member(Name/Arity, UserPreds),
|
|
935
|
+
catch(abolish(SessionId:Name/Arity), _, true))
|
|
936
|
+
; true
|
|
937
|
+
).
|
|
938
|
+
|
|
939
|
+
%% clear_session(+SessionId)
|
|
940
|
+
%% Empties the living module, ends all frames, and resets memories. Session
|
|
941
|
+
%% params (workspace/gas limit) are preserved so the REPL stays configured.
|
|
942
|
+
clear_session(SessionId) :-
|
|
943
|
+
( findall(FrameId, session_frame(FrameId, SessionId, _, _), FrameIds)
|
|
944
|
+
-> forall(member(FrameId, FrameIds), end_frame(FrameId))
|
|
945
|
+
; true
|
|
946
|
+
),
|
|
947
|
+
( session_engine(SessionId, Engine)
|
|
948
|
+
-> catch(engine_destroy(Engine), _, true)
|
|
949
|
+
; true
|
|
950
|
+
),
|
|
951
|
+
retractall(session_engine(SessionId, _)),
|
|
952
|
+
retractall(session_default_frame(SessionId, _)),
|
|
953
|
+
retractall(session_repl_memory(SessionId, _)),
|
|
954
|
+
assertz(session_repl_memory(SessionId, [])),
|
|
955
|
+
retractall(session_meta_memory(SessionId, _)),
|
|
956
|
+
assertz(session_meta_memory(SessionId, [])),
|
|
957
|
+
retractall(repl_snapshot(SessionId, _)),
|
|
958
|
+
retractall(session_user_tools(SessionId, _)),
|
|
959
|
+
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
960
|
+
repl_abolish_user_predicates(SessionId).
|
|
961
|
+
|
|
962
|
+
%% destroy_session(+SessionId)
|
|
963
|
+
%% Tears a REPL session down completely: clears the module/frames and removes
|
|
964
|
+
%% the session's dynamic bookkeeping so the process can host another session.
|
|
965
|
+
destroy_session(SessionId) :-
|
|
966
|
+
clear_session(SessionId),
|
|
967
|
+
retractall(session_repl_memory(SessionId, _)),
|
|
968
|
+
retractall(session_meta_memory(SessionId, _)),
|
|
969
|
+
retractall(session_repl_mode(SessionId, _)),
|
|
970
|
+
retractall(session_frozen(SessionId, _)),
|
|
971
|
+
retractall(session_locked(SessionId, _)),
|
|
972
|
+
retractall(session_directive(SessionId, _)),
|
|
973
|
+
retractall(session_meta_catalog(SessionId, _)),
|
|
974
|
+
retractall(session_dml_reference(SessionId, _)),
|
|
975
|
+
retractall(session_authoring_examples(SessionId, _)),
|
|
976
|
+
retractall(session_meta_template(SessionId, _)),
|
|
977
|
+
retractall(session_harness_examples(SessionId, _)),
|
|
978
|
+
retractall(session_prolog_snippets(SessionId, _)),
|
|
979
|
+
retractall(session_last_commit(SessionId, _)),
|
|
980
|
+
retractall(repl_snapshot(SessionId, _)),
|
|
981
|
+
retractall(session_params(SessionId, _)),
|
|
982
|
+
retractall(session_trace_enabled(SessionId, _)),
|
|
983
|
+
retractall(session_trace_log(SessionId, _)).
|
|
984
|
+
|
|
985
|
+
%% load_code(+SessionId, +Code, -Error)
|
|
986
|
+
%% Parses DML source into the session module, running the normal source
|
|
987
|
+
%% preprocessing (task/prompt rewrite, interpolation, expand_term).
|
|
988
|
+
load_code(SessionId, Code, Error) :-
|
|
989
|
+
catch(
|
|
990
|
+
( guard_not_frozen(SessionId),
|
|
991
|
+
parse_dml_string(Code, SessionId),
|
|
992
|
+
Error = none
|
|
993
|
+
),
|
|
994
|
+
LoadError,
|
|
995
|
+
format(atom(Error), '~w', [LoadError])
|
|
996
|
+
).
|
|
997
|
+
|
|
998
|
+
%% repl_initial_state(+SessionId, +FrameKind, -InitialState)
|
|
999
|
+
%% Builds the state{} threaded through a REPL frame. M1 seeds harness memory
|
|
1000
|
+
%% from session_repl_memory/2; M4 adds the meta/scratch slots.
|
|
1001
|
+
repl_initial_state(SessionId, FrameKind, InitialState) :-
|
|
1002
|
+
session_params(SessionId, Params),
|
|
1003
|
+
( get_dict(gas_limit, Params, GasLimit)
|
|
1004
|
+
-> true
|
|
1005
|
+
; GasLimit = -1
|
|
1006
|
+
),
|
|
1007
|
+
% Two-level memory: meta (code-writing) and harness (runtime) are kept
|
|
1008
|
+
% apart. The state carries the slot so write-back goes to the right one.
|
|
1009
|
+
( FrameKind == meta
|
|
1010
|
+
-> MemorySlot = meta,
|
|
1011
|
+
( session_meta_memory(SessionId, Memory)
|
|
1012
|
+
-> true
|
|
1013
|
+
; Memory = []
|
|
1014
|
+
)
|
|
1015
|
+
; MemorySlot = harness,
|
|
1016
|
+
( session_repl_memory(SessionId, Memory)
|
|
1017
|
+
-> true
|
|
1018
|
+
; Memory = []
|
|
1019
|
+
)
|
|
1020
|
+
),
|
|
1021
|
+
InitialState = state{
|
|
1022
|
+
memory: Memory,
|
|
1023
|
+
params: Params,
|
|
1024
|
+
context_stack: [],
|
|
1025
|
+
depth: 0,
|
|
1026
|
+
gas_remaining: GasLimit,
|
|
1027
|
+
memory_slot: MemorySlot,
|
|
1028
|
+
frame_kind: FrameKind
|
|
1029
|
+
}.
|
|
1030
|
+
|
|
1031
|
+
%% query_start(+SessionId, +Text, +FrameKind, +InitialState, -Error)
|
|
1032
|
+
%% Parses and preprocesses a goal string, then creates a frame running
|
|
1033
|
+
%% repl_solve/4. The frame becomes the session's default frame so that
|
|
1034
|
+
%% step_engine/4 and the runner can drive it.
|
|
1035
|
+
query_start(SessionId, Text, FrameKind, InitialState, Error) :-
|
|
1036
|
+
catch(
|
|
1037
|
+
( read_repl_goal(SessionId, Text, Goal, Names),
|
|
1038
|
+
create_frame_raw(SessionId, FrameKind, 0,
|
|
1039
|
+
deepclause_mi:repl_solve(Goal, Names, SessionId, InitialState),
|
|
1040
|
+
FrameId),
|
|
1041
|
+
retractall(session_default_frame(SessionId, _)),
|
|
1042
|
+
assertz(session_default_frame(SessionId, FrameId)),
|
|
1043
|
+
session_frame_engine(FrameId, Engine),
|
|
1044
|
+
retractall(session_engine(SessionId, _)),
|
|
1045
|
+
assertz(session_engine(SessionId, Engine)),
|
|
1046
|
+
Error = none
|
|
1047
|
+
),
|
|
1048
|
+
QueryError,
|
|
1049
|
+
format(atom(Error), '~w', [QueryError])
|
|
1050
|
+
).
|
|
1051
|
+
|
|
1052
|
+
%% ensure_full_stop(+Text, -TextWithStop)
|
|
1053
|
+
%% Appends a terminating full stop + newline when the goal text lacks one, so
|
|
1054
|
+
%% both `goal: p(X).` and `-g "p(X)"` read cleanly.
|
|
1055
|
+
ensure_full_stop(Text, Text1) :-
|
|
1056
|
+
atom_string(TextAtom, Text),
|
|
1057
|
+
( atom_ends_with_stop(TextAtom)
|
|
1058
|
+
-> atom_concat(TextAtom, '\n', Text1)
|
|
1059
|
+
; atom_concat(TextAtom, '.\n', Text1)
|
|
1060
|
+
).
|
|
1061
|
+
|
|
1062
|
+
%% atom_ends_with_stop(+Atom)
|
|
1063
|
+
%% True when the last non-whitespace character is a full stop. A naive
|
|
1064
|
+
%% "ends with ." test would append an extra full stop to any block that ends
|
|
1065
|
+
%% with a newline, and read_term/3 then fails on the lone '.' (end_of_clause).
|
|
1066
|
+
atom_ends_with_stop(TextAtom) :-
|
|
1067
|
+
atom_string(TextAtom, TextStr),
|
|
1068
|
+
string_length(TextStr, Len0),
|
|
1069
|
+
trim_trailing_ws(TextStr, Len0, Len),
|
|
1070
|
+
Len > 0,
|
|
1071
|
+
Pos is Len - 1,
|
|
1072
|
+
sub_string(TextStr, Pos, 1, _, ".").
|
|
1073
|
+
|
|
1074
|
+
trim_trailing_ws(Str, Len0, Len) :-
|
|
1075
|
+
( Len0 > 0,
|
|
1076
|
+
Pos is Len0 - 1,
|
|
1077
|
+
sub_string(Str, Pos, 1, _, Ch),
|
|
1078
|
+
( Ch == " " ; Ch == "\t" ; Ch == "\n" ; Ch == "\r" )
|
|
1079
|
+
-> Len1 is Len0 - 1,
|
|
1080
|
+
trim_trailing_ws(Str, Len1, Len)
|
|
1081
|
+
; Len = Len0
|
|
1082
|
+
).
|
|
1083
|
+
|
|
1084
|
+
%% read_repl_goal(+SessionId, +Text, -Goal, -Names)
|
|
1085
|
+
%% Names is the read_term variable_names list (Name=Var), used to render
|
|
1086
|
+
%% bindings for solution events.
|
|
1087
|
+
read_repl_goal(SessionId, Text, Goal, Names) :-
|
|
1088
|
+
% read_term/3 requires a full stop; REPL callers may omit it (`-g "p(X)"`).
|
|
1089
|
+
ensure_full_stop(Text, Text1),
|
|
1090
|
+
open_string(Text1, Stream),
|
|
1091
|
+
read_term(Stream, RawGoal, [module(SessionId), variable_names(Names0)]),
|
|
1092
|
+
close(Stream),
|
|
1093
|
+
( RawGoal == end_of_file
|
|
1094
|
+
-> throw(error(syntax_error('empty goal'), _))
|
|
1095
|
+
; true
|
|
1096
|
+
),
|
|
1097
|
+
% Apply only the goal-relevant source transforms. Do NOT run
|
|
1098
|
+
% preprocess_loaded_term/3 here: its file-load term expansion flattens a
|
|
1099
|
+
% list goal such as ["file.dml"] into its elements.
|
|
1100
|
+
transform_task_calls(RawGoal, Names0, Transformed),
|
|
1101
|
+
% Use the body expander (not expand_term_interp/3) so a `{Var}` string is
|
|
1102
|
+
% hoisted into a goal *before* the goal that uses it. expand_term_interp
|
|
1103
|
+
% embeds the interpolation term in the argument position instead, which is
|
|
1104
|
+
% wrong for a raw goal such as verify(X, "Is {X} > 0?", V).
|
|
1105
|
+
expand_body_interp(Transformed, Names0, Goal),
|
|
1106
|
+
Names = Names0.
|
|
1107
|
+
|
|
1108
|
+
%% repl_solve(+Goal, +Names, +SessionId, +State)
|
|
1109
|
+
%% Enumerating goal-mode solver. Yields one solution(Bindings, Deterministic)
|
|
1110
|
+
%% per answer and fails so engine_next/2 backtracks for the next solution.
|
|
1111
|
+
%% answer/1 commits (throws '$answer_commit') and stops enumeration.
|
|
1112
|
+
%% mi/3 is deliberately not used: it cuts after the first solution.
|
|
1113
|
+
repl_solve(Goal, Names, SessionId, State) :-
|
|
1114
|
+
nb_setval(current_session_id, SessionId),
|
|
1115
|
+
catch(
|
|
1116
|
+
repl_solve_loop(Goal, Names, SessionId, State),
|
|
1117
|
+
'$answer_commit',
|
|
1118
|
+
true
|
|
1119
|
+
).
|
|
1120
|
+
|
|
1121
|
+
repl_solve_loop(Goal, Names, SessionId, State) :-
|
|
1122
|
+
mi_call(Goal, State, StateOut),
|
|
1123
|
+
collect_bindings(Names, Bindings),
|
|
1124
|
+
store_memory_for_state(SessionId, StateOut),
|
|
1125
|
+
engine_yield(solution(Bindings, false)),
|
|
1126
|
+
fail.
|
|
1127
|
+
|
|
1128
|
+
%% collect_bindings(+Names, -Bindings)
|
|
1129
|
+
%% Bindings is a list of Name-RenderedText. Unbound variables render as '_'.
|
|
1130
|
+
collect_bindings(Names, Bindings) :-
|
|
1131
|
+
findall(Name-Text,
|
|
1132
|
+
( member(Name=Var, Names),
|
|
1133
|
+
render_binding(Var, Text)
|
|
1134
|
+
),
|
|
1135
|
+
Bindings).
|
|
1136
|
+
|
|
1137
|
+
render_binding(Var, '_') :-
|
|
1138
|
+
var(Var),
|
|
1139
|
+
!.
|
|
1140
|
+
render_binding(Value, Text) :-
|
|
1141
|
+
with_output_to(string(Text), write_term(Value, [quoted(true), max_depth(20)])).
|
|
1142
|
+
|
|
1143
|
+
%% store_memory_for_state(+SessionId, +State)
|
|
1144
|
+
%% Writes the threaded memory back to the slot named by the state.
|
|
1145
|
+
store_memory_for_state(SessionId, State) :-
|
|
1146
|
+
( is_dict(State),
|
|
1147
|
+
get_dict(memory, State, Memory)
|
|
1148
|
+
-> ( get_dict(memory_slot, State, meta)
|
|
1149
|
+
-> retractall(session_meta_memory(SessionId, _)),
|
|
1150
|
+
assertz(session_meta_memory(SessionId, Memory))
|
|
1151
|
+
; retractall(session_repl_memory(SessionId, _)),
|
|
1152
|
+
assertz(session_repl_memory(SessionId, Memory))
|
|
1153
|
+
)
|
|
1154
|
+
; true
|
|
1155
|
+
).
|
|
1156
|
+
|
|
1157
|
+
%% repl_listing(+SessionId, +Filter, -Text)
|
|
1158
|
+
%% Read-only listing used by the meta tool and the `listing.` REPL command.
|
|
1159
|
+
repl_listing(SessionId, Filter, Text) :-
|
|
1160
|
+
with_output_to(string(Text), repl_listing_output(SessionId, Filter)).
|
|
1161
|
+
|
|
1162
|
+
%% repl_listing(+Filter, -Text) uses the current session id.
|
|
1163
|
+
repl_listing(Filter, Text) :-
|
|
1164
|
+
get_session_id(SessionId),
|
|
1165
|
+
repl_listing(SessionId, Filter, Text).
|
|
1166
|
+
|
|
1167
|
+
repl_listing_output(SessionId, Filter) :-
|
|
1168
|
+
( Filter == tools
|
|
1169
|
+
-> forall(session_user_tool_schema(SessionId, Name, Schema),
|
|
1170
|
+
( SessionId:tool_source(Name, Source)
|
|
1171
|
+
-> format("~w - ~w~n~w~n~n", [Name, Schema, Source])
|
|
1172
|
+
; format("~w - ~w~n~n", [Name, Schema])
|
|
1173
|
+
)
|
|
1174
|
+
)
|
|
1175
|
+
; Filter == history
|
|
1176
|
+
-> repl_history_listing(SessionId)
|
|
1177
|
+
; ( current_predicate(SessionId:module_doc/1),
|
|
1178
|
+
SessionId:module_doc(Skill)
|
|
1179
|
+
-> format("skill: ~w~n~n", [Skill])
|
|
1180
|
+
; true
|
|
1181
|
+
),
|
|
1182
|
+
findall(Name/Arity,
|
|
1183
|
+
( current_predicate(SessionId:Name/Arity),
|
|
1184
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
1185
|
+
),
|
|
1186
|
+
Preds0),
|
|
1187
|
+
sort(Preds0, Preds),
|
|
1188
|
+
forall(member(Name/Arity, Preds),
|
|
1189
|
+
( repl_listing_match(Filter, Name, Arity)
|
|
1190
|
+
-> format("~w/~w~n", [Name, Arity]),
|
|
1191
|
+
( current_predicate(SessionId:doc/2),
|
|
1192
|
+
SessionId:doc(Name/Arity, DocText)
|
|
1193
|
+
-> format(" % ~w~n", [DocText])
|
|
1194
|
+
; true
|
|
1195
|
+
),
|
|
1196
|
+
functor(Head, Name, Arity),
|
|
1197
|
+
forall(clause(SessionId:Head, ClauseBody), print_user_clause(Head, ClauseBody))
|
|
1198
|
+
; true
|
|
1199
|
+
)
|
|
1200
|
+
)
|
|
1201
|
+
).
|
|
1202
|
+
|
|
1203
|
+
repl_listing_match(all, _, _) :- !.
|
|
1204
|
+
repl_listing_match(Name/Arity, Name, Arity) :- !.
|
|
1205
|
+
repl_listing_match(Filter, Name, _) :-
|
|
1206
|
+
atom(Filter),
|
|
1207
|
+
!,
|
|
1208
|
+
( sub_atom(Name, 0, _, _, Filter)
|
|
1209
|
+
; sub_atom(Filter, 0, _, _, Name)
|
|
1210
|
+
).
|
|
1211
|
+
repl_listing_match(_, _, _).
|
|
1212
|
+
|
|
1213
|
+
%% repl_user_predicate(+SessionId, +Name, +Arity)
|
|
1214
|
+
%% Excludes reserved and internal predicates from listing/export.
|
|
1215
|
+
repl_user_predicate(_SessionId, Name, _Arity) :-
|
|
1216
|
+
repl_reserved_name(Name),
|
|
1217
|
+
!,
|
|
1218
|
+
fail.
|
|
1219
|
+
repl_user_predicate(_SessionId, Name, _Arity) :-
|
|
1220
|
+
internal_session_predicate(Name),
|
|
1221
|
+
!,
|
|
1222
|
+
fail.
|
|
1223
|
+
repl_user_predicate(_SessionId, Name, _Arity) :-
|
|
1224
|
+
history_predicate(Name),
|
|
1225
|
+
!,
|
|
1226
|
+
fail.
|
|
1227
|
+
repl_user_predicate(SessionId, Name, Arity) :-
|
|
1228
|
+
% User DML clauses are asserted (interpreted) into the session module.
|
|
1229
|
+
% Exclude predicates imported via use_module (e.g. library(clpfd)) and
|
|
1230
|
+
% foreign/builtin predicates. Build a callable head rather than a predicate
|
|
1231
|
+
% indicator: predicate_property/2 wants a goal (Module:Goal).
|
|
1232
|
+
functor(Head, Name, Arity),
|
|
1233
|
+
predicate_property(SessionId:Head, interpreted),
|
|
1234
|
+
\+ predicate_property(SessionId:Head, imported_from(_)),
|
|
1235
|
+
\+ predicate_property(SessionId:Head, foreign).
|
|
1236
|
+
|
|
1237
|
+
internal_session_predicate(param).
|
|
1238
|
+
internal_session_predicate(param_decl).
|
|
1239
|
+
internal_session_predicate(tool_source).
|
|
1240
|
+
internal_session_predicate('__session__').
|
|
1241
|
+
|
|
1242
|
+
%% history_predicate(+Name)
|
|
1243
|
+
%% Runtime-owned harness-history facts. Excluded from the user predicate set (so
|
|
1244
|
+
%% they are neither listed as harness predicates nor exported nor replaced by a
|
|
1245
|
+
%% commit) and protected from user definition -- but CALLABLE from DML, so the
|
|
1246
|
+
%% harness can query what happened in previous sessions.
|
|
1247
|
+
history_predicate(Name) :-
|
|
1248
|
+
atom(Name),
|
|
1249
|
+
sub_atom(Name, 0, _, _, harness_).
|
|
1250
|
+
|
|
1251
|
+
%% repl_reserved_name(+Name)
|
|
1252
|
+
%% Names owned by the REPL runtime. M4 uses repl_reserved/1 to refuse goals
|
|
1253
|
+
%% that try to re-enter or shadow the REPL control surface.
|
|
1254
|
+
%% Any repl_*/meta_* name is reserved (namespace guard), plus the explicit
|
|
1255
|
+
%% control names below. repl_delete is a protected exception for definition
|
|
1256
|
+
%% purposes (see repl_protected_name/1).
|
|
1257
|
+
repl_reserved_name(Name) :-
|
|
1258
|
+
atom(Name),
|
|
1259
|
+
( sub_atom(Name, 0, _, _, repl_)
|
|
1260
|
+
; sub_atom(Name, 0, _, _, meta_)
|
|
1261
|
+
).
|
|
1262
|
+
|
|
1263
|
+
repl_reserved_name(repl_meta_prompt).
|
|
1264
|
+
repl_reserved_name(repl_extract_code).
|
|
1265
|
+
repl_reserved_name(repl_reserved).
|
|
1266
|
+
repl_reserved_name(repl_reserved_name).
|
|
1267
|
+
repl_reserved_name(repl_tool_catalog).
|
|
1268
|
+
repl_reserved_name(repl_listing).
|
|
1269
|
+
repl_reserved_name(repl_assert).
|
|
1270
|
+
repl_reserved_name(repl_retract).
|
|
1271
|
+
repl_reserved_name(repl_delete).
|
|
1272
|
+
repl_reserved_name(repl_freeze).
|
|
1273
|
+
repl_reserved_name(repl_unfreeze).
|
|
1274
|
+
repl_reserved_name(repl_is_frozen).
|
|
1275
|
+
repl_reserved_name(repl_solve).
|
|
1276
|
+
repl_reserved_name(repl_initial_state).
|
|
1277
|
+
repl_reserved_name(repl_user_predicate).
|
|
1278
|
+
repl_reserved_name(query_start).
|
|
1279
|
+
repl_reserved_name(read_repl_goal).
|
|
1280
|
+
repl_reserved_name(create_session).
|
|
1281
|
+
repl_reserved_name(clear_session).
|
|
1282
|
+
repl_reserved_name(destroy_session).
|
|
1283
|
+
repl_reserved_name(create_frame).
|
|
1284
|
+
repl_reserved_name(create_frame_raw).
|
|
1285
|
+
repl_reserved_name(step_frame).
|
|
1286
|
+
repl_reserved_name(end_frame).
|
|
1287
|
+
repl_reserved_name(frame_info).
|
|
1288
|
+
repl_reserved_name(default_frame).
|
|
1289
|
+
repl_reserved_name(load_code).
|
|
1290
|
+
repl_reserved_name(freeze).
|
|
1291
|
+
repl_reserved_name(unfreeze).
|
|
1292
|
+
repl_reserved_name(reset).
|
|
1293
|
+
repl_reserved_name(export).
|
|
1294
|
+
|
|
1295
|
+
%% repl_reserved(+TermOrNameArity)
|
|
1296
|
+
repl_reserved(Name) :-
|
|
1297
|
+
atom(Name),
|
|
1298
|
+
repl_reserved_name(Name).
|
|
1299
|
+
repl_reserved(Name/Arity) :-
|
|
1300
|
+
atom(Name),
|
|
1301
|
+
integer(Arity),
|
|
1302
|
+
repl_reserved_name(Name).
|
|
1303
|
+
repl_reserved(Term) :-
|
|
1304
|
+
compound(Term),
|
|
1305
|
+
functor(Term, Name, _),
|
|
1306
|
+
repl_reserved_name(Name).
|
|
1307
|
+
|
|
1308
|
+
%% ============================================================
|
|
1309
|
+
%% Freeze Lock and Export (M2)
|
|
1310
|
+
%% ============================================================
|
|
1311
|
+
|
|
1312
|
+
%% repl_freeze(+SessionId)
|
|
1313
|
+
%% Makes the living module read-only. Queries, `execute`, and nested task()
|
|
1314
|
+
%% calls still work; only mutation is refused.
|
|
1315
|
+
repl_freeze(SessionId) :-
|
|
1316
|
+
retractall(session_frozen(SessionId, _)),
|
|
1317
|
+
assertz(session_frozen(SessionId, true)).
|
|
1318
|
+
|
|
1319
|
+
%% repl_lock(+SessionId)
|
|
1320
|
+
%% Irreversibly freezes: repl_unfreeze/1 refuses. Used by --locked.
|
|
1321
|
+
repl_lock(SessionId) :-
|
|
1322
|
+
repl_freeze(SessionId),
|
|
1323
|
+
retractall(session_locked(SessionId, _)),
|
|
1324
|
+
assertz(session_locked(SessionId, true)).
|
|
1325
|
+
|
|
1326
|
+
%% repl_unfreeze(+SessionId)
|
|
1327
|
+
repl_unfreeze(SessionId) :-
|
|
1328
|
+
( session_locked(SessionId, true)
|
|
1329
|
+
-> throw(error(permission_error(modify, locked_harness, SessionId),
|
|
1330
|
+
context(repl_unfreeze/1, 'harness is locked; unfreeze is disabled')))
|
|
1331
|
+
; retractall(session_frozen(SessionId, _)),
|
|
1332
|
+
assertz(session_frozen(SessionId, false))
|
|
1333
|
+
).
|
|
1334
|
+
|
|
1335
|
+
%% repl_is_frozen(+SessionId)
|
|
1336
|
+
repl_is_frozen(SessionId) :-
|
|
1337
|
+
session_frozen(SessionId, true),
|
|
1338
|
+
!.
|
|
1339
|
+
|
|
1340
|
+
%% repl_try_unfreeze(+SessionId, -Error)
|
|
1341
|
+
%% Non-throwing wrapper so the host can report a locked harness cleanly.
|
|
1342
|
+
repl_try_unfreeze(SessionId, Error) :-
|
|
1343
|
+
catch(repl_unfreeze(SessionId), UnfreezeError,
|
|
1344
|
+
format(atom(Error), '~w', [UnfreezeError])),
|
|
1345
|
+
( var(Error)
|
|
1346
|
+
-> Error = none
|
|
1347
|
+
; true
|
|
1348
|
+
).
|
|
1349
|
+
|
|
1350
|
+
%% guard_not_frozen(+SessionId)
|
|
1351
|
+
%% Shared mutation guard for assert/retract/consult/load.
|
|
1352
|
+
guard_not_frozen(SessionId) :-
|
|
1353
|
+
( session_frozen(SessionId, true)
|
|
1354
|
+
-> throw(error(permission_error(modify, frozen_harness, SessionId),
|
|
1355
|
+
context(mi_call/3, 'harness is frozen; assert/retract/load are disabled')))
|
|
1356
|
+
; true
|
|
1357
|
+
).
|
|
1358
|
+
|
|
1359
|
+
%% record_directive(+SessionId, +Directive)
|
|
1360
|
+
record_directive(SessionId, Directive) :-
|
|
1361
|
+
( session_directive(SessionId, Directive)
|
|
1362
|
+
-> true
|
|
1363
|
+
; assertz(session_directive(SessionId, Directive))
|
|
1364
|
+
).
|
|
1365
|
+
|
|
1366
|
+
%% ---- Export (serialization) ----
|
|
1367
|
+
|
|
1368
|
+
%% export_harness(+SessionId, +Header, -Text)
|
|
1369
|
+
%% Deterministically serialize the living module into self-contained DML:
|
|
1370
|
+
%% directives, tools, embedded scripts, then user predicates sorted name/arity.
|
|
1371
|
+
export_harness(SessionId, Header, Text) :-
|
|
1372
|
+
with_output_to(string(Text), export_harness_output(SessionId, Header)).
|
|
1373
|
+
|
|
1374
|
+
export_harness_output(SessionId, Header) :-
|
|
1375
|
+
( Header == none
|
|
1376
|
+
-> true
|
|
1377
|
+
; format("% ~w~n", [Header])
|
|
1378
|
+
),
|
|
1379
|
+
export_directives(SessionId),
|
|
1380
|
+
export_tools(SessionId),
|
|
1381
|
+
export_clauses(SessionId).
|
|
1382
|
+
|
|
1383
|
+
export_directives(SessionId) :-
|
|
1384
|
+
findall(Directive, session_directive(SessionId, Directive), Directives0),
|
|
1385
|
+
sort(Directives0, Directives),
|
|
1386
|
+
forall(member(Directive, Directives), format(":- ~q.~n", [Directive])),
|
|
1387
|
+
( Directives == []
|
|
1388
|
+
-> true
|
|
1389
|
+
; nl
|
|
1390
|
+
).
|
|
1391
|
+
|
|
1392
|
+
export_tools(SessionId) :-
|
|
1393
|
+
findall(Name, session_user_tool_schema(SessionId, Name, _), Names0),
|
|
1394
|
+
sort(Names0, Names),
|
|
1395
|
+
forall(member(Name, Names),
|
|
1396
|
+
( SessionId:tool_source(Name, Source)
|
|
1397
|
+
-> format("~w~n~n", [Source])
|
|
1398
|
+
; true
|
|
1399
|
+
)).
|
|
1400
|
+
|
|
1401
|
+
export_clauses(SessionId) :-
|
|
1402
|
+
findall(Name/Arity,
|
|
1403
|
+
( current_predicate(SessionId:Name/Arity),
|
|
1404
|
+
export_user_predicate(SessionId, Name, Arity)
|
|
1405
|
+
),
|
|
1406
|
+
Preds0),
|
|
1407
|
+
sort(Preds0, Preds),
|
|
1408
|
+
forall(member(Name/Arity, Preds),
|
|
1409
|
+
( functor(Head, Name, Arity),
|
|
1410
|
+
forall(clause(SessionId:Head, Body), export_clause(Head, Body))
|
|
1411
|
+
)).
|
|
1412
|
+
|
|
1413
|
+
export_user_predicate(SessionId, Name, Arity) :-
|
|
1414
|
+
repl_user_predicate(SessionId, Name, Arity),
|
|
1415
|
+
\+ tool_predicate_name(Name).
|
|
1416
|
+
|
|
1417
|
+
%% export_clause(+Head, +Body)
|
|
1418
|
+
%% Copy + numbervars the clause so variable names are deterministic (A, B, …),
|
|
1419
|
+
%% which is required for byte-stable export → reload → export.
|
|
1420
|
+
%% export_stats(+SessionId, -Clauses, -Tools, -Scripts)
|
|
1421
|
+
export_stats(SessionId, Clauses, Tools, Scripts) :-
|
|
1422
|
+
findall(Name/Arity,
|
|
1423
|
+
( current_predicate(SessionId:Name/Arity),
|
|
1424
|
+
export_user_predicate(SessionId, Name, Arity)
|
|
1425
|
+
),
|
|
1426
|
+
Preds0),
|
|
1427
|
+
sort(Preds0, Preds),
|
|
1428
|
+
findall(Count,
|
|
1429
|
+
( member(PredName/PredArity, Preds),
|
|
1430
|
+
functor(PredHead, PredName, PredArity),
|
|
1431
|
+
aggregate_all(count, clause(SessionId:PredHead, _), Count)
|
|
1432
|
+
),
|
|
1433
|
+
PredCounts),
|
|
1434
|
+
sum_list(PredCounts, Clauses),
|
|
1435
|
+
findall(Name, session_user_tool_schema(SessionId, Name, _), ToolNames0),
|
|
1436
|
+
sort(ToolNames0, ToolNames),
|
|
1437
|
+
length(ToolNames, Tools),
|
|
1438
|
+
( current_predicate(SessionId:script_file/2)
|
|
1439
|
+
-> findall(Path, SessionId:script_file(_, Path), ScriptPaths0),
|
|
1440
|
+
sort(ScriptPaths0, ScriptPaths),
|
|
1441
|
+
length(ScriptPaths, Scripts)
|
|
1442
|
+
; Scripts = 0
|
|
1443
|
+
).
|
|
1444
|
+
|
|
1445
|
+
export_clause(Head, Body) :-
|
|
1446
|
+
print_user_clause(Head, Body),
|
|
1447
|
+
nl.
|
|
1448
|
+
|
|
1449
|
+
%% format_tool_source(+Head, +Description, +Body, -SourceCode)
|
|
1450
|
+
%% Source text for a tool, used by `listing(tools)` and export. Reverses the
|
|
1451
|
+
%% load-time task/prompt rewrites so readers see `task/N`, not `task_named/3`.
|
|
1452
|
+
format_tool_source(Head, none, Body, SourceCode) :-
|
|
1453
|
+
!,
|
|
1454
|
+
copy_term(Head-Body, NH-NB),
|
|
1455
|
+
numbervars(NH-NB, 0, _),
|
|
1456
|
+
clean_clause_body(NB, CleanBody),
|
|
1457
|
+
( CleanBody == true
|
|
1458
|
+
-> format(string(SourceCode), "tool(~q).", [NH])
|
|
1459
|
+
; format(string(SourceCode), "tool(~q) :-~n ~q.", [NH, CleanBody])
|
|
1460
|
+
).
|
|
1461
|
+
format_tool_source(Head, Description, Body, SourceCode) :-
|
|
1462
|
+
copy_term(Head-Description-Body, NH-ND-NB),
|
|
1463
|
+
numbervars(NH-ND-NB, 0, _),
|
|
1464
|
+
clean_clause_body(NB, CleanBody),
|
|
1465
|
+
( CleanBody == true
|
|
1466
|
+
-> format(string(SourceCode), "tool(~q, ~q).", [NH, ND])
|
|
1467
|
+
; format(string(SourceCode), "tool(~q, ~q) :-~n ~q.", [NH, ND, CleanBody])
|
|
1468
|
+
).
|
|
1469
|
+
|
|
1470
|
+
%% print_user_clause(+Head, +Body)
|
|
1471
|
+
%% Copy + numbervars for deterministic variable names, reverse the load-time
|
|
1472
|
+
%% task/prompt rewrites (see clean_clause_body/2), then print in source form.
|
|
1473
|
+
print_user_clause(Head, Body) :-
|
|
1474
|
+
copy_term(Head-Body, NumberedHead-NumberedBody),
|
|
1475
|
+
numbervars(NumberedHead-NumberedBody, 0, _),
|
|
1476
|
+
clean_clause_body(NumberedBody, CleanBody),
|
|
1477
|
+
( CleanBody == true
|
|
1478
|
+
-> format("~q.~n", [NumberedHead])
|
|
1479
|
+
; format("~q :-~n ~q.~n", [NumberedHead, CleanBody])
|
|
1480
|
+
).
|
|
1481
|
+
|
|
1482
|
+
tool_predicate_name(tool).
|
|
1483
|
+
tool_predicate_name(tool_source).
|
|
1484
|
+
|
|
1485
|
+
%% repl_script_files(+SessionId, -Paths)
|
|
1486
|
+
%% Workspace-relative files linked by script_file/2 facts; the host bundles them.
|
|
1487
|
+
repl_script_files(SessionId, Paths) :-
|
|
1488
|
+
( current_predicate(SessionId:script_file/2)
|
|
1489
|
+
-> % Only literal paths count; a stray variable path is ignored.
|
|
1490
|
+
findall(Path,
|
|
1491
|
+
( SessionId:script_file(_, Path),
|
|
1492
|
+
(atom(Path) ; string(Path))
|
|
1493
|
+
),
|
|
1494
|
+
Paths0),
|
|
1495
|
+
sort(Paths0, Paths)
|
|
1496
|
+
; Paths = []
|
|
1497
|
+
).
|
|
1498
|
+
|
|
1499
|
+
%% repl_materialize_embedded(+SessionId)
|
|
1500
|
+
%% Writes every embedded_file(Path, Content) fact to its workspace path. Used
|
|
1501
|
+
%% after loading a single-file .dml export (where linked scripts are inlined).
|
|
1502
|
+
repl_materialize_embedded(SessionId) :-
|
|
1503
|
+
( current_predicate(SessionId:embedded_file/2)
|
|
1504
|
+
-> forall(SessionId:embedded_file(Path, Content),
|
|
1505
|
+
( resolve_workspace_path(Path, Full),
|
|
1506
|
+
catch(
|
|
1507
|
+
( file_directory_name(Full, Dir),
|
|
1508
|
+
make_directory_path(Dir),
|
|
1509
|
+
setup_call_cleanup(
|
|
1510
|
+
open(Full, write, Stream),
|
|
1511
|
+
format(Stream, "~s", [Content]),
|
|
1512
|
+
close(Stream))
|
|
1513
|
+
),
|
|
1514
|
+
_,
|
|
1515
|
+
true
|
|
1516
|
+
)
|
|
1517
|
+
))
|
|
1518
|
+
; true
|
|
1519
|
+
).
|
|
1520
|
+
|
|
1521
|
+
%% ============================================================
|
|
1522
|
+
%% Prompt Mode (M3)
|
|
1523
|
+
%% ============================================================
|
|
1524
|
+
|
|
1525
|
+
%% repl_protected_name(+Name)
|
|
1526
|
+
%% Reserved names that generated code may not (re)define. repl_delete is a
|
|
1527
|
+
%% legitimate harness directive and is allowed through.
|
|
1528
|
+
repl_protected_name(Name) :-
|
|
1529
|
+
history_predicate(Name).
|
|
1530
|
+
repl_protected_name(Name) :-
|
|
1531
|
+
repl_reserved_name(Name),
|
|
1532
|
+
Name \== repl_delete.
|
|
1533
|
+
|
|
1534
|
+
repl_protected_clause((Head :- _)) :-
|
|
1535
|
+
callable(Head),
|
|
1536
|
+
functor(Head, Name, _),
|
|
1537
|
+
repl_protected_name(Name).
|
|
1538
|
+
repl_protected_clause(Head) :-
|
|
1539
|
+
callable(Head),
|
|
1540
|
+
functor(Head, Name, _),
|
|
1541
|
+
repl_protected_name(Name).
|
|
1542
|
+
|
|
1543
|
+
%% repl_set_meta_catalog(+SessionId, +CatalogText)
|
|
1544
|
+
repl_set_meta_catalog(SessionId, CatalogText) :-
|
|
1545
|
+
retractall(session_meta_catalog(SessionId, _)),
|
|
1546
|
+
assertz(session_meta_catalog(SessionId, CatalogText)).
|
|
1547
|
+
|
|
1548
|
+
%% repl_prepare_single(+SessionId, +ClauseText, -PreparedTerms)
|
|
1549
|
+
%% Reads exactly ONE source term and runs the SAME load-time preprocessing as
|
|
1550
|
+
%% parse_dml_string/2: transform_task_calls/3 (task/N, prompt/N ->
|
|
1551
|
+
%% task_named/3, prompt_named/3), expand_interpolations/3 ({Var} strings), and
|
|
1552
|
+
%% expand_term/2 (e.g. DCG rules). Scratch asserts must be preprocessed exactly
|
|
1553
|
+
%% like a file load, or a clause asserted mid-turn keeps task/N, which has no
|
|
1554
|
+
%% runtime handler and fails silently.
|
|
1555
|
+
%%
|
|
1556
|
+
%% Trailing content is an error so `assert` can never silently drop clauses that
|
|
1557
|
+
%% `assert_many` would have kept.
|
|
1558
|
+
repl_prepare_single(SessionId, ClauseText, PreparedTerms) :-
|
|
1559
|
+
ensure_full_stop(ClauseText, Text1),
|
|
1560
|
+
open_string(Text1, Stream),
|
|
1561
|
+
read_term(Stream, Term, [module(SessionId), variable_names(Bindings)]),
|
|
1562
|
+
( read_term(Stream, Extra, [module(SessionId)]),
|
|
1563
|
+
Extra \== end_of_file
|
|
1564
|
+
-> close(Stream),
|
|
1565
|
+
throw(error(syntax_error('assert expects a single clause; use assert_many for multiple'), _))
|
|
1566
|
+
; close(Stream)
|
|
1567
|
+
),
|
|
1568
|
+
preprocess_loaded_term(Term, Bindings, PreparedTerms).
|
|
1569
|
+
|
|
1570
|
+
%% repl_read_clause(+SessionId, +ClauseText, -Clause)
|
|
1571
|
+
%% Reads exactly one term without preprocessing. Used by retract, which only
|
|
1572
|
+
%% needs the predicate's name/arity. Trailing content is an error.
|
|
1573
|
+
repl_read_clause(SessionId, ClauseText, Clause) :-
|
|
1574
|
+
ensure_full_stop(ClauseText, Text1),
|
|
1575
|
+
open_string(Text1, Stream),
|
|
1576
|
+
read_term(Stream, Clause, [module(SessionId)]),
|
|
1577
|
+
( read_term(Stream, Extra, [module(SessionId)]),
|
|
1578
|
+
Extra \== end_of_file
|
|
1579
|
+
-> close(Stream),
|
|
1580
|
+
throw(error(syntax_error('retract expects a single clause pattern'), _))
|
|
1581
|
+
; close(Stream)
|
|
1582
|
+
).
|
|
1583
|
+
|
|
1584
|
+
%% repl_head_of(+Term, -Head)
|
|
1585
|
+
%% Strips a rule body, so retracting with a full clause pattern targets the
|
|
1586
|
+
%% predicate instead of accidentally matching every rule ((:-)/2).
|
|
1587
|
+
repl_head_of((Head :- _Body), Head) :- !.
|
|
1588
|
+
repl_head_of(Head, Head).
|
|
1589
|
+
|
|
1590
|
+
%% repl_assert_clause(+PreparedTerm, +SessionId)
|
|
1591
|
+
%% Asserts one already-preprocessed term, rejecting reserved names.
|
|
1592
|
+
repl_assert_clause(PreparedTerm, SessionId) :-
|
|
1593
|
+
( repl_protected_clause(PreparedTerm)
|
|
1594
|
+
-> throw(error(permission_error(modify, reserved_name, PreparedTerm),
|
|
1595
|
+
context(repl_assert/3, 'cannot define a reserved predicate')))
|
|
1596
|
+
; process_clause(PreparedTerm, SessionId)
|
|
1597
|
+
).
|
|
1598
|
+
|
|
1599
|
+
%% repl_assert(+SessionId, +ClauseText, -Result)
|
|
1600
|
+
%% Parses and asserts generated DML with the load-time preprocessing applied,
|
|
1601
|
+
%% so scratch-asserted clauses behave exactly like loaded ones.
|
|
1602
|
+
%% Refused when frozen; reserved names are rejected.
|
|
1603
|
+
repl_assert(SessionId, ClauseText, Result) :-
|
|
1604
|
+
catch(
|
|
1605
|
+
( guard_not_frozen(SessionId),
|
|
1606
|
+
repl_prepare_single(SessionId, ClauseText, PreparedTerms),
|
|
1607
|
+
forall(member(PreparedTerm, PreparedTerms),
|
|
1608
|
+
repl_assert_clause(PreparedTerm, SessionId)),
|
|
1609
|
+
Result = ok
|
|
1610
|
+
),
|
|
1611
|
+
AssertError,
|
|
1612
|
+
format(atom(Result), 'error(~w)', [AssertError])
|
|
1613
|
+
).
|
|
1614
|
+
|
|
1615
|
+
%% repl_set_dml_reference(+SessionId, +Text)
|
|
1616
|
+
%% Stores the bundled DML reference so the meta prompt can include it.
|
|
1617
|
+
repl_set_dml_reference(SessionId, Text) :-
|
|
1618
|
+
retractall(session_dml_reference(SessionId, _)),
|
|
1619
|
+
assertz(session_dml_reference(SessionId, Text)).
|
|
1620
|
+
|
|
1621
|
+
%% repl_set_authoring_examples(+SessionId, +Text)
|
|
1622
|
+
%% Stores the curated authoring patterns for the meta prompt.
|
|
1623
|
+
repl_set_authoring_examples(SessionId, Text) :-
|
|
1624
|
+
retractall(session_authoring_examples(SessionId, _)),
|
|
1625
|
+
assertz(session_authoring_examples(SessionId, Text)).
|
|
1626
|
+
|
|
1627
|
+
%% repl_set_meta_template(+SessionId, +Template)
|
|
1628
|
+
%% Stores the meta-prompt template (the bundled META_PROMPT.md). Placeholders
|
|
1629
|
+
%% {{CATALOG}}, {{REFERENCE}}, {{EXAMPLES}}, {{MANIFEST}}, {{MEMORY}}, {{REQUEST}}
|
|
1630
|
+
%% are filled by repl_meta_prompt/3.
|
|
1631
|
+
repl_set_meta_template(SessionId, Template) :-
|
|
1632
|
+
retractall(session_meta_template(SessionId, _)),
|
|
1633
|
+
assertz(session_meta_template(SessionId, Template)).
|
|
1634
|
+
|
|
1635
|
+
%% repl_set_harness_examples(+SessionId, +Text)
|
|
1636
|
+
%% Stores the bundled HARNESS_EXAMPLES.md ({{EXAMPLES}}).
|
|
1637
|
+
repl_set_harness_examples(SessionId, Text) :-
|
|
1638
|
+
retractall(session_harness_examples(SessionId, _)),
|
|
1639
|
+
assertz(session_harness_examples(SessionId, Text)).
|
|
1640
|
+
|
|
1641
|
+
%% repl_set_prolog_snippets(+SessionId, +Text)
|
|
1642
|
+
%% Stores the bundled PROLOG_SNIPPETS.md ({{SNIPPETS}}).
|
|
1643
|
+
repl_set_prolog_snippets(SessionId, Text) :-
|
|
1644
|
+
retractall(session_prolog_snippets(SessionId, _)),
|
|
1645
|
+
assertz(session_prolog_snippets(SessionId, Text)).
|
|
1646
|
+
|
|
1647
|
+
%% repl_parse_clauses(+SessionId, +ClausesText, -Terms)
|
|
1648
|
+
%% Parse and preprocess a block of DML clauses without asserting them.
|
|
1649
|
+
repl_parse_clauses(SessionId, ClausesText, Terms) :-
|
|
1650
|
+
ensure_full_stop(ClausesText, Text1),
|
|
1651
|
+
open_string(Text1, Stream),
|
|
1652
|
+
repl_read_prepared_terms(Stream, SessionId, Terms),
|
|
1653
|
+
close(Stream).
|
|
1654
|
+
|
|
1655
|
+
repl_read_prepared_terms(Stream, SessionId, Terms) :-
|
|
1656
|
+
read_term(Stream, Term, [module(SessionId), variable_names(Bindings)]),
|
|
1657
|
+
( Term == end_of_file
|
|
1658
|
+
-> Terms = []
|
|
1659
|
+
; % Apply the same preprocessing as parse_dml_string/2 so scratch clauses
|
|
1660
|
+
% match loaded ones (task/prompt rewrite, interpolation, expand_term).
|
|
1661
|
+
preprocess_loaded_term(Term, Bindings, Prepared),
|
|
1662
|
+
% Directives (e.g. use_module(library(clpfd))) must run while parsing so
|
|
1663
|
+
% their operators are visible to the terms that follow; keep them out of
|
|
1664
|
+
% the returned list so repl_commit_terms does not process them twice.
|
|
1665
|
+
partition_directives(Prepared, Rules, Directives),
|
|
1666
|
+
forall(member(Directive, Directives), process_clause(Directive, SessionId)),
|
|
1667
|
+
append(Rules, Rest, Terms),
|
|
1668
|
+
repl_read_prepared_terms(Stream, SessionId, Rest)
|
|
1669
|
+
).
|
|
1670
|
+
|
|
1671
|
+
partition_directives([], [], []).
|
|
1672
|
+
partition_directives([Term|Terms], Rules, Directives) :-
|
|
1673
|
+
( Term = (:- _)
|
|
1674
|
+
-> Rules = Rules0,
|
|
1675
|
+
Directives = [Term|Directives0]
|
|
1676
|
+
; Rules = [Term|Rules0],
|
|
1677
|
+
Directives = Directives0
|
|
1678
|
+
),
|
|
1679
|
+
partition_directives(Terms, Rules0, Directives0).
|
|
1680
|
+
|
|
1681
|
+
%% repl_commit_terms(+SessionId, +Terms)
|
|
1682
|
+
%% Assert already-preprocessed terms (the shared tail of assert_many/define).
|
|
1683
|
+
repl_commit_terms(SessionId, Terms) :-
|
|
1684
|
+
forall(member(PreparedTerm, Terms),
|
|
1685
|
+
( repl_protected_clause(PreparedTerm)
|
|
1686
|
+
-> throw(error(permission_error(modify, reserved_name, PreparedTerm),
|
|
1687
|
+
context(repl_assert_many/3, 'cannot define a reserved predicate')))
|
|
1688
|
+
; process_clause(PreparedTerm, SessionId)
|
|
1689
|
+
)).
|
|
1690
|
+
|
|
1691
|
+
%% repl_assert_many(+SessionId, +ClausesText, -Result)
|
|
1692
|
+
%% Parses a block of clauses and asserts them in the order written. Prefer this
|
|
1693
|
+
%% for defining a predicate: clause order matters for backtracking, so asserting
|
|
1694
|
+
%% one clause at a time can produce a wrong program.
|
|
1695
|
+
repl_assert_many(SessionId, ClausesText, Result) :-
|
|
1696
|
+
catch(
|
|
1697
|
+
( guard_not_frozen(SessionId),
|
|
1698
|
+
repl_parse_clauses(SessionId, ClausesText, Terms),
|
|
1699
|
+
repl_commit_terms(SessionId, Terms),
|
|
1700
|
+
repl_note_touched(SessionId, Terms),
|
|
1701
|
+
Result = ok
|
|
1702
|
+
),
|
|
1703
|
+
AssertError,
|
|
1704
|
+
format(atom(Result), 'error(~w)', [AssertError])
|
|
1705
|
+
).
|
|
1706
|
+
|
|
1707
|
+
%% repl_term_indicator(+Term, -Name/Arity)
|
|
1708
|
+
%% The plain predicate a parsed clause defines. Directives and tool/2 wrappers
|
|
1709
|
+
%% have no plain indicator here (process_clause owns them).
|
|
1710
|
+
repl_term_indicator((:- _), _) :- !, fail.
|
|
1711
|
+
repl_term_indicator((tool(_, _) :- _), _) :- !, fail.
|
|
1712
|
+
repl_term_indicator((tool(_) :- _), _) :- !, fail.
|
|
1713
|
+
repl_term_indicator(Term, Name/Arity) :-
|
|
1714
|
+
repl_head_of(Term, Head),
|
|
1715
|
+
callable(Head),
|
|
1716
|
+
functor(Head, Name, Arity).
|
|
1717
|
+
|
|
1718
|
+
%% repl_note_touched(+SessionId, +Terms)
|
|
1719
|
+
%% Record the predicate indicators a term list defines, for history recording.
|
|
1720
|
+
repl_note_touched(SessionId, Terms) :-
|
|
1721
|
+
forall(( member(Term, Terms), repl_term_indicator(Term, Name/Arity) ),
|
|
1722
|
+
( session_edit_touched(SessionId, Name/Arity)
|
|
1723
|
+
-> true
|
|
1724
|
+
; assertz(session_edit_touched(SessionId, Name/Arity))
|
|
1725
|
+
)).
|
|
1726
|
+
|
|
1727
|
+
%% repl_edit_touched(+SessionId, -Indicators)
|
|
1728
|
+
%% Predicates touched by edit tools since the last clear (sorted, unique).
|
|
1729
|
+
repl_edit_touched(SessionId, Indicators) :-
|
|
1730
|
+
findall(Name/Arity, session_edit_touched(SessionId, Name/Arity), Raw),
|
|
1731
|
+
sort(Raw, Indicators).
|
|
1732
|
+
|
|
1733
|
+
%% repl_edit_touched_text(+SessionId, -Text)
|
|
1734
|
+
%% "Name/Arity, Name/Arity" of the predicates an agent turn touched.
|
|
1735
|
+
repl_edit_touched_text(SessionId, Text) :-
|
|
1736
|
+
repl_edit_touched(SessionId, Indicators),
|
|
1737
|
+
with_output_to(string(Text), print_pred_list(Indicators)).
|
|
1738
|
+
|
|
1739
|
+
repl_edit_touched_clear(SessionId) :-
|
|
1740
|
+
retractall(session_edit_touched(SessionId, _)).
|
|
1741
|
+
|
|
1742
|
+
%% repl_callable_indicator(+Head, -Name, -Arity)
|
|
1743
|
+
%% Validate a predicate head: an ordinary (non-operator, non-system) functor.
|
|
1744
|
+
repl_callable_indicator(Head, Name, Arity) :-
|
|
1745
|
+
callable(Head),
|
|
1746
|
+
functor(Head, Name, Arity),
|
|
1747
|
+
atom(Name),
|
|
1748
|
+
Name \== '[]',
|
|
1749
|
+
Name \== '{}',
|
|
1750
|
+
\+ repl_system_functor(Name/Arity).
|
|
1751
|
+
|
|
1752
|
+
%% Functors that are list/operator/system structure, never user predicates.
|
|
1753
|
+
repl_system_functor(Name/_) :-
|
|
1754
|
+
member(Name, ['.', ',', ';', ':-', '->', ':', '|', '!', '\\+', '=', 'is']).
|
|
1755
|
+
|
|
1756
|
+
%% repl_retract(+SessionId, +ClausePattern, -Result)
|
|
1757
|
+
%% Retracts matching user clauses. Reserved names are protected.
|
|
1758
|
+
repl_retract(SessionId, ClauseText, Result) :-
|
|
1759
|
+
catch(
|
|
1760
|
+
( guard_not_frozen(SessionId),
|
|
1761
|
+
repl_read_clause(SessionId, ClauseText, Pattern),
|
|
1762
|
+
repl_head_of(Pattern, Clause),
|
|
1763
|
+
repl_callable_indicator(Clause, Name, Arity),
|
|
1764
|
+
( repl_protected_clause(Clause)
|
|
1765
|
+
-> throw(error(permission_error(modify, reserved_name, Clause),
|
|
1766
|
+
context(repl_retract/3, 'cannot retract a reserved predicate')))
|
|
1767
|
+
; true
|
|
1768
|
+
),
|
|
1769
|
+
functor(Head, Name, Arity),
|
|
1770
|
+
retractall(SessionId:Head),
|
|
1771
|
+
( session_user_tools(SessionId, Name)
|
|
1772
|
+
-> retractall(session_user_tools(SessionId, Name)),
|
|
1773
|
+
retractall(session_user_tool_schema(SessionId, Name, _)),
|
|
1774
|
+
retractall(SessionId:tool_source(Name, _))
|
|
1775
|
+
; true
|
|
1776
|
+
),
|
|
1777
|
+
( session_edit_touched(SessionId, Name/Arity)
|
|
1778
|
+
-> true
|
|
1779
|
+
; assertz(session_edit_touched(SessionId, Name/Arity))
|
|
1780
|
+
),
|
|
1781
|
+
Result = ok
|
|
1782
|
+
),
|
|
1783
|
+
RetractError,
|
|
1784
|
+
format(atom(Result), 'error(~w)', [RetractError])
|
|
1785
|
+
).
|
|
1786
|
+
|
|
1787
|
+
%% repl_delete_predicate(+SessionId, +Name, +Arity, -Result)
|
|
1788
|
+
%% Fully remove a predicate. Unlike retract (which only removes matching
|
|
1789
|
+
%% clauses and can leave an empty predicate behind), this abolishes it.
|
|
1790
|
+
repl_delete_predicate(SessionId, Name, Arity, Result) :-
|
|
1791
|
+
catch(
|
|
1792
|
+
( guard_not_frozen(SessionId),
|
|
1793
|
+
atom(Name), integer(Arity), Arity >= 0,
|
|
1794
|
+
( repl_protected_name(Name)
|
|
1795
|
+
-> throw(error(permission_error(modify, reserved_name, Name/Arity),
|
|
1796
|
+
context(repl_delete_predicate/4, 'cannot delete a reserved predicate')))
|
|
1797
|
+
; true
|
|
1798
|
+
),
|
|
1799
|
+
catch(abolish(SessionId:Name/Arity), _, true),
|
|
1800
|
+
( session_user_tools(SessionId, Name)
|
|
1801
|
+
-> retractall(session_user_tools(SessionId, Name)),
|
|
1802
|
+
retractall(session_user_tool_schema(SessionId, Name, _)),
|
|
1803
|
+
retractall(SessionId:tool_source(Name, _))
|
|
1804
|
+
; true
|
|
1805
|
+
),
|
|
1806
|
+
( session_edit_touched(SessionId, Name/Arity)
|
|
1807
|
+
-> true
|
|
1808
|
+
; assertz(session_edit_touched(SessionId, Name/Arity))
|
|
1809
|
+
),
|
|
1810
|
+
Result = ok
|
|
1811
|
+
),
|
|
1812
|
+
DeleteError,
|
|
1813
|
+
format(atom(Result), 'error(~w)', [DeleteError])
|
|
1814
|
+
).
|
|
1815
|
+
|
|
1816
|
+
%% repl_define(+SessionId, +HeadPatternText, +ClausesText, -Result)
|
|
1817
|
+
%% Replace every predicate the clause block defines. It parses the block,
|
|
1818
|
+
%% computes its indicators (plus the head pattern's), abolishes each, then
|
|
1819
|
+
%% asserts the block. This makes `define` safe no matter how many predicates a
|
|
1820
|
+
%% block spans, so a multi-predicate block cannot append duplicate clauses.
|
|
1821
|
+
repl_define(SessionId, HeadPatternText, ClausesText, Result) :-
|
|
1822
|
+
catch(
|
|
1823
|
+
( guard_not_frozen(SessionId),
|
|
1824
|
+
repl_read_clause(SessionId, HeadPatternText, Pattern),
|
|
1825
|
+
repl_head_of(Pattern, HeadClause),
|
|
1826
|
+
repl_callable_indicator(HeadClause, HeadName, HeadArity),
|
|
1827
|
+
repl_parse_clauses(SessionId, ClausesText, Terms),
|
|
1828
|
+
findall(Name/Arity,
|
|
1829
|
+
( member(Term, Terms), repl_term_indicator(Term, Name/Arity) ),
|
|
1830
|
+
BlockIndicators0),
|
|
1831
|
+
sort([HeadName/HeadArity|BlockIndicators0], Indicators),
|
|
1832
|
+
forall(member(Name/Arity, Indicators),
|
|
1833
|
+
( repl_protected_name(Name)
|
|
1834
|
+
-> throw(error(permission_error(modify, reserved_name, Name/Arity),
|
|
1835
|
+
context(repl_define/4, 'cannot define a reserved predicate')))
|
|
1836
|
+
; catch(abolish(SessionId:Name/Arity), _, true),
|
|
1837
|
+
( session_user_tools(SessionId, Name)
|
|
1838
|
+
-> retractall(session_user_tools(SessionId, Name)),
|
|
1839
|
+
retractall(session_user_tool_schema(SessionId, Name, _)),
|
|
1840
|
+
retractall(SessionId:tool_source(Name, _))
|
|
1841
|
+
; true
|
|
1842
|
+
)
|
|
1843
|
+
)),
|
|
1844
|
+
repl_commit_terms(SessionId, Terms),
|
|
1845
|
+
repl_note_touched(SessionId, Terms),
|
|
1846
|
+
Result = ok
|
|
1847
|
+
),
|
|
1848
|
+
DefineError,
|
|
1849
|
+
format(atom(Result), 'error(~w)', [DefineError])
|
|
1850
|
+
).
|
|
1851
|
+
|
|
1852
|
+
%% repl_extract_code(+Text, -Source)
|
|
1853
|
+
%% Extracts a ```dml fenced block; falls back to the whole answer.
|
|
1854
|
+
repl_extract_code(Text, Source) :-
|
|
1855
|
+
atom_string(TextAtom, Text),
|
|
1856
|
+
( sub_string(TextAtom, Start0, _, _, "```dml")
|
|
1857
|
+
-> Start is Start0 + 6,
|
|
1858
|
+
% After = 0 takes the remainder of the text (an unbound After would
|
|
1859
|
+
% bind Rest to the empty prefix first).
|
|
1860
|
+
sub_string(TextAtom, Start, _, 0, Rest),
|
|
1861
|
+
% Search for the closing fence by offset: binding Before=0 here would
|
|
1862
|
+
% require the fence to be at the very start of Rest.
|
|
1863
|
+
( sub_string(Rest, FenceEnd, _, _, "```")
|
|
1864
|
+
-> sub_string(Rest, 0, FenceEnd, _, Source)
|
|
1865
|
+
; Source = Rest
|
|
1866
|
+
)
|
|
1867
|
+
; Source = TextAtom
|
|
1868
|
+
).
|
|
1869
|
+
|
|
1870
|
+
%% repl_manifest(+SessionId, -ManifestText)
|
|
1871
|
+
%% Compact signatures + tools + entries + directives for the meta prompt.
|
|
1872
|
+
repl_manifest(SessionId, Manifest) :-
|
|
1873
|
+
findall(Name/Arity,
|
|
1874
|
+
( current_predicate(SessionId:Name/Arity),
|
|
1875
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
1876
|
+
),
|
|
1877
|
+
Preds0),
|
|
1878
|
+
sort(Preds0, Preds),
|
|
1879
|
+
findall(Name, session_user_tool_schema(SessionId, Name, _), Tools0),
|
|
1880
|
+
sort(Tools0, Tools),
|
|
1881
|
+
findall(Directive, session_directive(SessionId, Directive), Directives0),
|
|
1882
|
+
sort(Directives0, Directives),
|
|
1883
|
+
( current_predicate(SessionId:module_doc/1),
|
|
1884
|
+
SessionId:module_doc(Summary0)
|
|
1885
|
+
-> Summary = Summary0
|
|
1886
|
+
; Summary = ""
|
|
1887
|
+
),
|
|
1888
|
+
( current_predicate(SessionId:doc/2)
|
|
1889
|
+
-> findall(PI-DocText, SessionId:doc(PI, DocText), Docs0)
|
|
1890
|
+
; Docs0 = []
|
|
1891
|
+
),
|
|
1892
|
+
repl_history_summary(SessionId, HistorySummary),
|
|
1893
|
+
with_output_to(string(Manifest),
|
|
1894
|
+
( ( Summary == ""
|
|
1895
|
+
-> format("- skill: (none; add module_doc/1 so the harness describes itself and can be routed)~n")
|
|
1896
|
+
; format("- skill: ~w~n", [Summary])
|
|
1897
|
+
),
|
|
1898
|
+
format("- predicates: ~w~n", [Preds]),
|
|
1899
|
+
format("- tools: ~w~n", [Tools]),
|
|
1900
|
+
format("- directives: ~w~n", [Directives]),
|
|
1901
|
+
( Docs0 == []
|
|
1902
|
+
-> format("- docs: (none; add doc(Name/Arity, Text) facts)~n")
|
|
1903
|
+
; forall(member(PI-DocText, Docs0), format("- doc(~w): ~w~n", [PI, DocText]))
|
|
1904
|
+
),
|
|
1905
|
+
format("- history: ~w~n", [HistorySummary])
|
|
1906
|
+
)).
|
|
1907
|
+
|
|
1908
|
+
%% repl_meta_memory_text(+SessionId, -Text)
|
|
1909
|
+
repl_meta_memory_text(SessionId, Text) :-
|
|
1910
|
+
( session_meta_memory(SessionId, Memory)
|
|
1911
|
+
-> true
|
|
1912
|
+
; Memory = []
|
|
1913
|
+
),
|
|
1914
|
+
last_n(Memory, 10, Recent),
|
|
1915
|
+
with_output_to(string(Text),
|
|
1916
|
+
forall(member(message{role: Role, content: Content}, Recent),
|
|
1917
|
+
format("~w: ~w~n", [Role, Content]))).
|
|
1918
|
+
|
|
1919
|
+
%% repl_memory_term(+SessionId, +Slot, -TermText)
|
|
1920
|
+
%% Serializes a live memory slot (harness|meta) as a Prolog term so a session
|
|
1921
|
+
%% can persist it and restore it on `--session` resume.
|
|
1922
|
+
repl_memory_term(SessionId, Slot, Text) :-
|
|
1923
|
+
( Slot == meta
|
|
1924
|
+
-> ( session_meta_memory(SessionId, Memory)
|
|
1925
|
+
-> true
|
|
1926
|
+
; Memory = []
|
|
1927
|
+
)
|
|
1928
|
+
; ( session_repl_memory(SessionId, Memory)
|
|
1929
|
+
-> true
|
|
1930
|
+
; Memory = []
|
|
1931
|
+
)
|
|
1932
|
+
),
|
|
1933
|
+
with_output_to(string(Text),
|
|
1934
|
+
write_term(Memory, [quoted(true), max_depth(0)])).
|
|
1935
|
+
|
|
1936
|
+
last_n(List, N, Tail) :-
|
|
1937
|
+
length(List, Len),
|
|
1938
|
+
( Len =< N
|
|
1939
|
+
-> Tail = List
|
|
1940
|
+
; Skip is Len - N,
|
|
1941
|
+
length(Prefix, Skip),
|
|
1942
|
+
append(Prefix, Tail, List)
|
|
1943
|
+
).
|
|
1944
|
+
|
|
1945
|
+
%% ============================================================
|
|
1946
|
+
%% Harness history (see docs/DML_REPL_SESSION_HISTORY.md)
|
|
1947
|
+
%% ============================================================
|
|
1948
|
+
|
|
1949
|
+
%% repl_history_load(+SessionId, +Text)
|
|
1950
|
+
%% Asserts harness_* history facts (the persisted .deepclause/repl/history.dml)
|
|
1951
|
+
%% into the session module so the harness can query prior sessions.
|
|
1952
|
+
repl_history_load(SessionId, Text) :-
|
|
1953
|
+
( var(Text)
|
|
1954
|
+
-> true
|
|
1955
|
+
; open_string(Text, Stream),
|
|
1956
|
+
repl_history_read_stream(Stream, SessionId),
|
|
1957
|
+
close(Stream)
|
|
1958
|
+
).
|
|
1959
|
+
|
|
1960
|
+
repl_history_read_stream(Stream, SessionId) :-
|
|
1961
|
+
catch(read_term(Stream, Term, [module(SessionId)]), _, Term = end_of_file),
|
|
1962
|
+
( Term == end_of_file
|
|
1963
|
+
-> true
|
|
1964
|
+
; repl_history_assert_term(Term, SessionId),
|
|
1965
|
+
repl_history_read_stream(Stream, SessionId)
|
|
1966
|
+
).
|
|
1967
|
+
|
|
1968
|
+
%% repl_history_record(+SessionId, +FactText)
|
|
1969
|
+
%% Asserts one or more history facts into the live module. The recorder persists
|
|
1970
|
+
%% the same facts to history.dml; here they become queryable.
|
|
1971
|
+
repl_history_record(SessionId, FactText) :-
|
|
1972
|
+
( var(FactText)
|
|
1973
|
+
-> true
|
|
1974
|
+
; ensure_full_stop(FactText, Text1),
|
|
1975
|
+
open_string(Text1, Stream),
|
|
1976
|
+
repl_history_read_stream(Stream, SessionId),
|
|
1977
|
+
close(Stream)
|
|
1978
|
+
).
|
|
1979
|
+
|
|
1980
|
+
%% Only harness_* facts may be asserted through this path.
|
|
1981
|
+
repl_history_assert_term(Term, SessionId) :-
|
|
1982
|
+
( nonvar(Term),
|
|
1983
|
+
Term \== end_of_file,
|
|
1984
|
+
Term \== none,
|
|
1985
|
+
callable(Term),
|
|
1986
|
+
functor(Term, Name, _),
|
|
1987
|
+
history_predicate(Name)
|
|
1988
|
+
-> % harness_head is a singleton: keep the latest (rollback moves it back).
|
|
1989
|
+
( Name == harness_head
|
|
1990
|
+
-> retractall(SessionId:harness_head(_, _, _, _))
|
|
1991
|
+
; true
|
|
1992
|
+
),
|
|
1993
|
+
% Migrate pre-/5 harness_change facts (they carried an unused Entry arg).
|
|
1994
|
+
( Term = harness_change(V, Ts, Kind, Sid, Turn, _Entry)
|
|
1995
|
+
-> assertz(SessionId:harness_change(V, Ts, Kind, Sid, Turn))
|
|
1996
|
+
; assertz(SessionId:Term)
|
|
1997
|
+
)
|
|
1998
|
+
; true
|
|
1999
|
+
).
|
|
2000
|
+
|
|
2001
|
+
%% repl_rollback(+SessionId, +Source, -Result)
|
|
2002
|
+
%% Restores the living module from a history version's serialized source:
|
|
2003
|
+
%% abolishes the user predicates and reloads the source (directives, tools and
|
|
2004
|
+
%% entries come from the source too). Refused when frozen. Runtime conversation
|
|
2005
|
+
%% memory is preserved.
|
|
2006
|
+
repl_rollback(SessionId, Source, Result) :-
|
|
2007
|
+
catch(
|
|
2008
|
+
( guard_not_frozen(SessionId),
|
|
2009
|
+
repl_abolish_user_predicates(SessionId),
|
|
2010
|
+
retractall(session_directive(SessionId, _)),
|
|
2011
|
+
retractall(session_user_tools(SessionId, _)),
|
|
2012
|
+
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
2013
|
+
( current_predicate(SessionId:tool_source/2)
|
|
2014
|
+
-> retractall(SessionId:tool_source(_, _))
|
|
2015
|
+
; true
|
|
2016
|
+
),
|
|
2017
|
+
( Source == ""
|
|
2018
|
+
-> Result = ok
|
|
2019
|
+
; load_code(SessionId, Source, LoadError),
|
|
2020
|
+
( LoadError == none
|
|
2021
|
+
-> Result = ok
|
|
2022
|
+
; format(atom(Result), 'error(~w)', [LoadError])
|
|
2023
|
+
)
|
|
2024
|
+
)
|
|
2025
|
+
),
|
|
2026
|
+
RollbackError,
|
|
2027
|
+
format(atom(Result), 'error(~w)', [RollbackError])
|
|
2028
|
+
).
|
|
2029
|
+
|
|
2030
|
+
%% repl_user_predicates(+SessionId, -Preds)
|
|
2031
|
+
%% Sorted Name/Arity list of the harness's own predicates.
|
|
2032
|
+
repl_user_predicates(SessionId, Preds) :-
|
|
2033
|
+
findall(Name/Arity,
|
|
2034
|
+
( current_predicate(SessionId:Name/Arity),
|
|
2035
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
2036
|
+
),
|
|
2037
|
+
Preds0),
|
|
2038
|
+
sort(Preds0, Preds).
|
|
2039
|
+
|
|
2040
|
+
%% repl_user_predicates_text(+SessionId, -Text)
|
|
2041
|
+
%% Rendered "name/arity, name/arity" for change diffing on the host side.
|
|
2042
|
+
repl_user_predicates_text(SessionId, Text) :-
|
|
2043
|
+
repl_user_predicates(SessionId, Preds),
|
|
2044
|
+
with_output_to(string(Text), print_pred_list(Preds)).
|
|
2045
|
+
|
|
2046
|
+
%% repl_predicate_arities(+SessionId, +Name, -Arities)
|
|
2047
|
+
%% Sorted arities of a user predicate, e.g. agent_main -> [0,1,2]. Used by
|
|
2048
|
+
%% `deepclause run` to pick the agent_main/N to call for the given arguments.
|
|
2049
|
+
repl_predicate_arities(SessionId, Name, Arities) :-
|
|
2050
|
+
atom(Name),
|
|
2051
|
+
findall(Arity,
|
|
2052
|
+
( current_predicate(SessionId:Name/Arity),
|
|
2053
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
2054
|
+
),
|
|
2055
|
+
Arities0),
|
|
2056
|
+
sort(Arities0, Arities).
|
|
2057
|
+
|
|
2058
|
+
%% ============================================================
|
|
2059
|
+
%% Nondeterminism probe (end_turn advisory)
|
|
2060
|
+
%%
|
|
2061
|
+
%% After a commit, the host may probe each changed pure predicate: if calling it
|
|
2062
|
+
%% with fresh variables yields more than a threshold of solutions, the predicate
|
|
2063
|
+
%% is probably missing a commit (`once/1` / `!`) and would flood an `execute`.
|
|
2064
|
+
%% Side-effecting predicates are never run (see repl_impure_name/1), fact tables
|
|
2065
|
+
%% are skipped, and the search is bounded by an inference limit.
|
|
2066
|
+
%% ============================================================
|
|
2067
|
+
|
|
2068
|
+
%% Functors with side effects: model calls, host tools, file IO, DB mutation.
|
|
2069
|
+
repl_impure_name(task). repl_impure_name(prompt). repl_impure_name(exec).
|
|
2070
|
+
repl_impure_name(system). repl_impure_name(user). repl_impure_name(answer).
|
|
2071
|
+
repl_impure_name(input). repl_impure_name(output). repl_impure_name(read_media).
|
|
2072
|
+
repl_impure_name(attach_media). repl_impure_name(save_image). repl_impure_name(gen_image).
|
|
2073
|
+
repl_impure_name(judge). repl_impure_name(choose). repl_impure_name(rate).
|
|
2074
|
+
repl_impure_name(verify). repl_impure_name(probability).
|
|
2075
|
+
repl_impure_name(write_file). repl_impure_name(append_file). repl_impure_name(delete_file).
|
|
2076
|
+
repl_impure_name(make_directory_path). repl_impure_name(open). repl_impure_name(close).
|
|
2077
|
+
repl_impure_name(assert). repl_impure_name(assertz). repl_impure_name(asserta).
|
|
2078
|
+
repl_impure_name(retract). repl_impure_name(retractall).
|
|
2079
|
+
repl_impure_name(consult). repl_impure_name(use_module). repl_impure_name(load_files).
|
|
2080
|
+
|
|
2081
|
+
%% repl_body_impure(+Goal) — true when the goal tree calls a side-effecting name.
|
|
2082
|
+
repl_body_impure(Goal) :-
|
|
2083
|
+
callable(Goal),
|
|
2084
|
+
functor(Goal, Name, _),
|
|
2085
|
+
repl_impure_name(Name),
|
|
2086
|
+
!.
|
|
2087
|
+
repl_body_impure((A, _)) :- repl_body_impure(A).
|
|
2088
|
+
repl_body_impure((_, B)) :- repl_body_impure(B).
|
|
2089
|
+
repl_body_impure((A ; B)) :- ( repl_body_impure(A) ; repl_body_impure(B) ).
|
|
2090
|
+
repl_body_impure((A -> B)) :- ( repl_body_impure(A) ; repl_body_impure(B) ).
|
|
2091
|
+
repl_body_impure(\+ Goal) :- repl_body_impure(Goal).
|
|
2092
|
+
repl_body_impure(once(Goal)) :- repl_body_impure(Goal).
|
|
2093
|
+
repl_body_impure(ignore(Goal)) :- repl_body_impure(Goal).
|
|
2094
|
+
repl_body_impure(forall(Gen, Action)) :- ( repl_body_impure(Gen) ; repl_body_impure(Action) ).
|
|
2095
|
+
repl_body_impure(catch(Goal, _, Recovery)) :- ( repl_body_impure(Goal) ; repl_body_impure(Recovery) ).
|
|
2096
|
+
repl_body_impure(findall(_, Goal, _)) :- repl_body_impure(Goal).
|
|
2097
|
+
repl_body_impure(findall(_, Goal, _, _)) :- repl_body_impure(Goal).
|
|
2098
|
+
repl_body_impure(call_with_inference_limit(Goal, _, _)) :- repl_body_impure(Goal).
|
|
2099
|
+
repl_body_impure(with_output_to(_, Goal)) :- repl_body_impure(Goal).
|
|
2100
|
+
repl_body_impure(_:Goal) :- repl_body_impure(Goal).
|
|
2101
|
+
|
|
2102
|
+
repl_predicate_pure(SessionId, Name, Arity) :-
|
|
2103
|
+
functor(Head, Name, Arity),
|
|
2104
|
+
\+ ( clause(SessionId:Head, Body), repl_body_impure(Body) ).
|
|
2105
|
+
|
|
2106
|
+
%% True when the predicate has at least one clause with a real body (not a fact).
|
|
2107
|
+
repl_predicate_has_rule(SessionId, Name, Arity) :-
|
|
2108
|
+
functor(Head, Name, Arity),
|
|
2109
|
+
clause(SessionId:Head, Body),
|
|
2110
|
+
Body \== true,
|
|
2111
|
+
!.
|
|
2112
|
+
|
|
2113
|
+
%% repl_probe_nondeterminism(+SessionId, +Name, +Arity, +Threshold, +Bound, -Result)
|
|
2114
|
+
%% Result: one | many | timeout | data | unsafe | removed | error.
|
|
2115
|
+
repl_probe_nondeterminism(SessionId, Name, Arity, Threshold, Bound, Result) :-
|
|
2116
|
+
( \+ current_predicate(SessionId:Name/Arity)
|
|
2117
|
+
-> Result = removed
|
|
2118
|
+
; \+ repl_predicate_has_rule(SessionId, Name, Arity)
|
|
2119
|
+
-> Result = data
|
|
2120
|
+
; \+ repl_predicate_pure(SessionId, Name, Arity)
|
|
2121
|
+
-> Result = unsafe
|
|
2122
|
+
; functor(Head, Name, Arity),
|
|
2123
|
+
N is Threshold + 1,
|
|
2124
|
+
catch(
|
|
2125
|
+
( with_output_to(string(_),
|
|
2126
|
+
call_with_inference_limit(call_nth(SessionId:Head, N), Bound, R)),
|
|
2127
|
+
( R == false -> Result = one
|
|
2128
|
+
; R == inference_limit_exceeded -> Result = timeout
|
|
2129
|
+
; R == true -> Result = many
|
|
2130
|
+
; R == (!) -> Result = many % success is returned as `!` in this build
|
|
2131
|
+
; Result = error
|
|
2132
|
+
)
|
|
2133
|
+
),
|
|
2134
|
+
_,
|
|
2135
|
+
Result = error
|
|
2136
|
+
)
|
|
2137
|
+
).
|
|
2138
|
+
|
|
2139
|
+
%% repl_history_listing(+SessionId)
|
|
2140
|
+
%% Human-readable tail for `listing(history)`.
|
|
2141
|
+
repl_history_listing(SessionId) :-
|
|
2142
|
+
( current_predicate(SessionId:harness_head/4),
|
|
2143
|
+
SessionId:harness_head(Version, Timestamp, HeadSession, _Turn)
|
|
2144
|
+
-> format("harness_head(~w, ~w, ~w)~n", [Version, Timestamp, HeadSession])
|
|
2145
|
+
; format("harness_head(none)~n")
|
|
2146
|
+
),
|
|
2147
|
+
( current_predicate(SessionId:harness_session/5)
|
|
2148
|
+
-> forall(SessionId:harness_session(Sid, Started, Model, Backend, Dir),
|
|
2149
|
+
format("harness_session(~w, ~w, ~w, ~w, ~w)~n", [Sid, Started, Model, Backend, Dir]))
|
|
2150
|
+
; true
|
|
2151
|
+
),
|
|
2152
|
+
( current_predicate(SessionId:harness_change/5)
|
|
2153
|
+
-> forall(SessionId:harness_change(V, Ts, Kind, Sid, Turn),
|
|
2154
|
+
format("harness_change(~w, ~w, ~w, ~w, ~w)~n", [V, Ts, Kind, Sid, Turn]))
|
|
2155
|
+
; true
|
|
2156
|
+
).
|
|
2157
|
+
|
|
2158
|
+
%% repl_history_summary(+SessionId, -Text)
|
|
2159
|
+
%% One-line history for the meta prompt manifest.
|
|
2160
|
+
repl_history_summary(SessionId, Text) :-
|
|
2161
|
+
( current_predicate(SessionId:harness_head/4),
|
|
2162
|
+
SessionId:harness_head(Version, Timestamp, HeadSession, _Turn)
|
|
2163
|
+
-> true
|
|
2164
|
+
; Version = none, Timestamp = none, HeadSession = none
|
|
2165
|
+
),
|
|
2166
|
+
( current_predicate(SessionId:harness_session/5)
|
|
2167
|
+
-> findall(s, SessionId:harness_session(_, _, _, _, _), Sessions0),
|
|
2168
|
+
length(Sessions0, Sessions)
|
|
2169
|
+
; Sessions = 0
|
|
2170
|
+
),
|
|
2171
|
+
( current_predicate(SessionId:harness_change/5)
|
|
2172
|
+
-> findall(c, SessionId:harness_change(_, _, _, _, _), Changes0),
|
|
2173
|
+
length(Changes0, Changes)
|
|
2174
|
+
; Changes = 0
|
|
2175
|
+
),
|
|
2176
|
+
format(atom(Text), 'version ~w (updated ~w by ~w), ~w session(s), ~w change(s)',
|
|
2177
|
+
[Version, Timestamp, HeadSession, Sessions, Changes]).
|
|
2178
|
+
|
|
2179
|
+
%% repl_meta_prompt(+SessionId, +Request, -PromptText)
|
|
2180
|
+
%% Assembles the deterministic code-writing prompt: authoring contract, host
|
|
2181
|
+
%% tool catalog, harness manifest, recent meta memory, the request, and the
|
|
2182
|
+
%% output contract.
|
|
2183
|
+
repl_meta_prompt(SessionId, Request, Prompt) :-
|
|
2184
|
+
( session_meta_catalog(SessionId, Catalog) -> true ; Catalog = "" ),
|
|
2185
|
+
( session_dml_reference(SessionId, Reference) -> true ; Reference = "" ),
|
|
2186
|
+
( session_authoring_examples(SessionId, Authoring) -> true ; Authoring = "" ),
|
|
2187
|
+
( session_harness_examples(SessionId, Examples) -> true ; Examples = "" ),
|
|
2188
|
+
( session_prolog_snippets(SessionId, Snippets) -> true ; Snippets = "" ),
|
|
2189
|
+
repl_manifest(SessionId, Manifest),
|
|
2190
|
+
repl_meta_memory_text(SessionId, MemoryText),
|
|
2191
|
+
% The template is the bundled META_PROMPT.md, injected by the host via
|
|
2192
|
+
% repl_set_meta_template/2. The fallback keeps the prompt usable when no
|
|
2193
|
+
% template was set (e.g. a direct repl_meta_prompt/3 call).
|
|
2194
|
+
( session_meta_template(SessionId, Template)
|
|
2195
|
+
-> true
|
|
2196
|
+
; Template = "You are the DeepClause REPL harness author.~n~nHOST TOOL CATALOG~n{{CATALOG}}~n~nUSER REQUEST~n{{REQUEST}}~n"
|
|
2197
|
+
),
|
|
2198
|
+
replace_all(Template, "{{CATALOG}}", Catalog, P1),
|
|
2199
|
+
replace_all(P1, "{{REFERENCE}}", Reference, P2),
|
|
2200
|
+
replace_all(P2, "{{AUTHORING}}", Authoring, P3),
|
|
2201
|
+
replace_all(P3, "{{EXAMPLES}}", Examples, P4),
|
|
2202
|
+
replace_all(P4, "{{SNIPPETS}}", Snippets, P5),
|
|
2203
|
+
replace_all(P5, "{{MANIFEST}}", Manifest, P6),
|
|
2204
|
+
replace_all(P6, "{{MEMORY}}", MemoryText, P7),
|
|
2205
|
+
replace_all(P7, "{{REQUEST}}", Request, Prompt).
|
|
2206
|
+
|
|
2207
|
+
%% replace_all(+Text, +From, +To, -Out)
|
|
2208
|
+
%% Literal, all-occurrences substring replacement. Fills the meta-prompt
|
|
2209
|
+
%% template's {{PLACEHOLDER}} sections.
|
|
2210
|
+
replace_all(Text, From, To, Out) :-
|
|
2211
|
+
string_codes(Text, TextCodes),
|
|
2212
|
+
string_codes(From, FromCodes),
|
|
2213
|
+
string_codes(To, ToCodes),
|
|
2214
|
+
replace_all_codes(TextCodes, FromCodes, ToCodes, OutCodes),
|
|
2215
|
+
string_codes(Out, OutCodes).
|
|
2216
|
+
|
|
2217
|
+
replace_all_codes([], _, _, []) :- !.
|
|
2218
|
+
replace_all_codes(Codes, From, To, Out) :-
|
|
2219
|
+
From \== [],
|
|
2220
|
+
( append(From, Rest, Codes)
|
|
2221
|
+
-> append(To, Tail, Out),
|
|
2222
|
+
replace_all_codes(Rest, From, To, Tail)
|
|
2223
|
+
; Codes = [C|Cs],
|
|
2224
|
+
Out = [C|Tail],
|
|
2225
|
+
replace_all_codes(Cs, From, To, Tail)
|
|
2226
|
+
).
|
|
2227
|
+
|
|
2228
|
+
repl_meta_prompt(Request, Prompt) :-
|
|
2229
|
+
get_session_id(SessionId),
|
|
2230
|
+
repl_meta_prompt(SessionId, Request, Prompt).
|
|
2231
|
+
|
|
2232
|
+
%% repl_run_meta(+SessionId, +PromptText, -FrameId, -Error)
|
|
2233
|
+
%% Creates the meta frame running the code-writing task.
|
|
2234
|
+
repl_run_meta(SessionId, PromptText, FrameId, Error) :-
|
|
2235
|
+
catch(
|
|
2236
|
+
( repl_initial_state(SessionId, meta, State),
|
|
2237
|
+
create_frame_raw(SessionId, meta, 0,
|
|
2238
|
+
deepclause_mi:repl_meta_solve(PromptText, SessionId, State),
|
|
2239
|
+
FrameId),
|
|
2240
|
+
Error = none
|
|
2241
|
+
),
|
|
2242
|
+
CreateError,
|
|
2243
|
+
format(atom(Error), '~w', [CreateError])
|
|
2244
|
+
).
|
|
2245
|
+
|
|
2246
|
+
repl_meta_solve(PromptText, SessionId, State) :-
|
|
2247
|
+
nb_setval(current_session_id, SessionId),
|
|
2248
|
+
catch(
|
|
2249
|
+
( mi_call(
|
|
2250
|
+
task_named(PromptText, [DMLCode, Answer],
|
|
2251
|
+
[typed_var{name:'DMLCode', type:string},
|
|
2252
|
+
typed_var{name:'Answer', type:string}]),
|
|
2253
|
+
State, _StateOut),
|
|
2254
|
+
% The task's own memory (incl. the assembled prompt and tool results)
|
|
2255
|
+
% is ephemeral. The host records a clean user/assistant pair with
|
|
2256
|
+
% repl_record_meta/3 after the turn; that becomes the next turn's
|
|
2257
|
+
% conversation history (a normal messages array).
|
|
2258
|
+
engine_yield(meta_result(DMLCode, Answer))
|
|
2259
|
+
),
|
|
2260
|
+
MetaError,
|
|
2261
|
+
( format(atom(Msg), '~w', [MetaError]),
|
|
2262
|
+
engine_yield(error(Msg))
|
|
2263
|
+
)
|
|
2264
|
+
).
|
|
2265
|
+
|
|
2266
|
+
%% repl_record_meta(+SessionId, +Request, +Summary)
|
|
2267
|
+
%% Appends a short outcome note to meta memory (bounded).
|
|
2268
|
+
repl_record_meta(SessionId, Request, Summary) :-
|
|
2269
|
+
( session_meta_memory(SessionId, Memory)
|
|
2270
|
+
-> true
|
|
2271
|
+
; Memory = []
|
|
2272
|
+
),
|
|
2273
|
+
append(Memory,
|
|
2274
|
+
[message{role: user, content: Request},
|
|
2275
|
+
message{role: assistant, content: Summary}],
|
|
2276
|
+
Combined),
|
|
2277
|
+
% Keep every system message (compaction checkpoints) and the last 20
|
|
2278
|
+
% conversational messages; naive last_n/3 would evict old checkpoints.
|
|
2279
|
+
findall(M, (member(M, Combined), repl_system_message(M)), SystemMessages),
|
|
2280
|
+
findall(M, (member(M, Combined), \+ repl_system_message(M)), Conversational),
|
|
2281
|
+
last_n(Conversational, 20, Recent),
|
|
2282
|
+
append(SystemMessages, Recent, Bounded),
|
|
2283
|
+
retractall(session_meta_memory(SessionId, _)),
|
|
2284
|
+
assertz(session_meta_memory(SessionId, Bounded)).
|
|
2285
|
+
|
|
2286
|
+
%% repl_system_message(+Message)
|
|
2287
|
+
repl_system_message(message{role: system, content: _}).
|
|
2288
|
+
|
|
2289
|
+
%% repl_get_memory(+SessionId, +Slot, -Messages)
|
|
2290
|
+
%% Read a live memory slot as the message list stored in the session.
|
|
2291
|
+
repl_get_memory(SessionId, Slot, Messages) :-
|
|
2292
|
+
( Slot == meta
|
|
2293
|
+
-> ( session_meta_memory(SessionId, Memory)
|
|
2294
|
+
-> true
|
|
2295
|
+
; Memory = []
|
|
2296
|
+
)
|
|
2297
|
+
; ( session_repl_memory(SessionId, Memory)
|
|
2298
|
+
-> true
|
|
2299
|
+
; Memory = []
|
|
2300
|
+
)
|
|
2301
|
+
),
|
|
2302
|
+
Messages = Memory.
|
|
2303
|
+
|
|
2304
|
+
%% repl_set_memory(+SessionId, +Slot, +Messages)
|
|
2305
|
+
%% Replace a live memory slot (used by host-side TypeScript compaction).
|
|
2306
|
+
repl_set_memory(SessionId, Slot, Messages) :-
|
|
2307
|
+
( Slot == meta
|
|
2308
|
+
-> retractall(session_meta_memory(SessionId, _)),
|
|
2309
|
+
assertz(session_meta_memory(SessionId, Messages))
|
|
2310
|
+
; retractall(session_repl_memory(SessionId, _)),
|
|
2311
|
+
assertz(session_repl_memory(SessionId, Messages))
|
|
2312
|
+
).
|
|
2313
|
+
|
|
2314
|
+
%% ---- Edit transactions ----
|
|
2315
|
+
|
|
2316
|
+
%% repl_snapshot_save(+SessionId)
|
|
2317
|
+
%% Snapshots the living module, tool bookkeeping, directives, and harness
|
|
2318
|
+
%% memory so a failed prompt leaves the harness untouched.
|
|
2319
|
+
repl_snapshot_save(SessionId) :-
|
|
2320
|
+
retractall(repl_snapshot(SessionId, _)),
|
|
2321
|
+
findall(Clause,
|
|
2322
|
+
( current_predicate(SessionId:Name/Arity),
|
|
2323
|
+
repl_user_predicate(SessionId, Name, Arity),
|
|
2324
|
+
functor(Head, Name, Arity),
|
|
2325
|
+
clause(SessionId:Head, Body),
|
|
2326
|
+
( Body == true
|
|
2327
|
+
-> Clause = Head
|
|
2328
|
+
; Clause = (Head :- Body)
|
|
2329
|
+
)
|
|
2330
|
+
),
|
|
2331
|
+
Clauses),
|
|
2332
|
+
findall(Name, session_user_tools(SessionId, Name), Tools),
|
|
2333
|
+
findall(Name-Schema, session_user_tool_schema(SessionId, Name, Schema), Schemas),
|
|
2334
|
+
( current_predicate(SessionId:tool_source/2)
|
|
2335
|
+
-> findall(Name-Source, SessionId:tool_source(Name, Source), ToolSources)
|
|
2336
|
+
; ToolSources = []
|
|
2337
|
+
),
|
|
2338
|
+
findall(Directive, session_directive(SessionId, Directive), Directives),
|
|
2339
|
+
( session_repl_memory(SessionId, Memory) -> true ; Memory = [] ),
|
|
2340
|
+
assertz(repl_snapshot(SessionId, snapshot{
|
|
2341
|
+
clauses: Clauses,
|
|
2342
|
+
tools: Tools,
|
|
2343
|
+
schemas: Schemas,
|
|
2344
|
+
tool_sources: ToolSources,
|
|
2345
|
+
directives: Directives,
|
|
2346
|
+
memory: Memory
|
|
2347
|
+
})).
|
|
2348
|
+
|
|
2349
|
+
%% repl_snapshot_restore(+SessionId)
|
|
2350
|
+
repl_snapshot_restore(SessionId) :-
|
|
2351
|
+
( repl_snapshot(SessionId, Snapshot)
|
|
2352
|
+
-> retract(repl_snapshot(SessionId, Snapshot)),
|
|
2353
|
+
findall(Name/Arity,
|
|
2354
|
+
( current_predicate(SessionId:Name/Arity),
|
|
2355
|
+
repl_user_predicate(SessionId, Name, Arity)
|
|
2356
|
+
),
|
|
2357
|
+
Preds),
|
|
2358
|
+
forall(member(AbName/AbArity, Preds),
|
|
2359
|
+
catch(abolish(SessionId:AbName/AbArity), _, true)),
|
|
2360
|
+
forall(member(Clause, Snapshot.clauses), assertz(SessionId:Clause)),
|
|
2361
|
+
retractall(session_user_tools(SessionId, _)),
|
|
2362
|
+
forall(member(Tool, Snapshot.tools), assertz(session_user_tools(SessionId, Tool))),
|
|
2363
|
+
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
2364
|
+
forall(member(SchemaName-Schema, Snapshot.schemas),
|
|
2365
|
+
assertz(session_user_tool_schema(SessionId, SchemaName, Schema))),
|
|
2366
|
+
( current_predicate(SessionId:tool_source/2)
|
|
2367
|
+
-> retractall(SessionId:tool_source(_, _))
|
|
2368
|
+
; true
|
|
2369
|
+
),
|
|
2370
|
+
forall(member(SourceName-Source, Snapshot.tool_sources),
|
|
2371
|
+
assertz(SessionId:tool_source(SourceName, Source))),
|
|
2372
|
+
retractall(session_directive(SessionId, _)),
|
|
2373
|
+
forall(member(Directive, Snapshot.directives),
|
|
2374
|
+
assertz(session_directive(SessionId, Directive))),
|
|
2375
|
+
retractall(session_repl_memory(SessionId, _)),
|
|
2376
|
+
assertz(session_repl_memory(SessionId, Snapshot.memory))
|
|
2377
|
+
; true
|
|
2378
|
+
).
|
|
2379
|
+
|
|
2380
|
+
%% repl_parse_source(+SessionId, +Source, -Preds, -Error)
|
|
2381
|
+
%% Validates generated DML and collects the predicates it defines.
|
|
2382
|
+
repl_parse_source(SessionId, Source, Preds, Error) :-
|
|
2383
|
+
catch(
|
|
2384
|
+
( open_string(Source, Stream),
|
|
2385
|
+
repl_collect_source_predicates(Stream, SessionId, Preds0),
|
|
2386
|
+
close(Stream),
|
|
2387
|
+
( member(Name0/_, Preds0),
|
|
2388
|
+
repl_protected_name(Name0)
|
|
2389
|
+
-> format(atom(Error), 'generated code defines reserved predicate ~w', [Name0])
|
|
2390
|
+
; sort(Preds0, Preds),
|
|
2391
|
+
Error = none
|
|
2392
|
+
)
|
|
2393
|
+
),
|
|
2394
|
+
ParseError,
|
|
2395
|
+
format(atom(Error), '~w', [ParseError])
|
|
2396
|
+
).
|
|
2397
|
+
|
|
2398
|
+
%% repl_collect_source_predicates(+Stream, +SessionId, -Preds)
|
|
2399
|
+
%% Reads with the session's operator table and executes `:- use_module(...)`
|
|
2400
|
+
%% directives as it goes, exactly like the load path. This is what lets library
|
|
2401
|
+
%% operators (CLP(FD)'s #=/ins, etc.) be used in committed source: without it,
|
|
2402
|
+
%% validation parsed the source with an empty operator table and rejected code
|
|
2403
|
+
%% that loads fine.
|
|
2404
|
+
repl_collect_source_predicates(Stream, SessionId, Preds) :-
|
|
2405
|
+
read_term(Stream, Term, [module(SessionId)]),
|
|
2406
|
+
( Term == end_of_file
|
|
2407
|
+
-> Preds = []
|
|
2408
|
+
; ( Term = (:- Directive)
|
|
2409
|
+
-> catch(process_directive(Directive, SessionId), _, true),
|
|
2410
|
+
Preds = Rest
|
|
2411
|
+
; Term = repl_delete(_)
|
|
2412
|
+
-> Preds = Rest
|
|
2413
|
+
; Term = (Head :- _)
|
|
2414
|
+
-> functor(Head, Name, Arity),
|
|
2415
|
+
Preds = [Name/Arity|Rest]
|
|
2416
|
+
; functor(Term, Name, Arity),
|
|
2417
|
+
Preds = [Name/Arity|Rest]
|
|
2418
|
+
),
|
|
2419
|
+
repl_collect_source_predicates(Stream, SessionId, Rest)
|
|
2420
|
+
).
|
|
2421
|
+
|
|
2422
|
+
%% repl_commit(+SessionId, +Source, -Result)
|
|
2423
|
+
%% Transactional commit: validate, replace-by-predicate, then load the returned
|
|
2424
|
+
%% source. Result = ok | 'error(Message)'. Never throws; failures are reported
|
|
2425
|
+
%% through Result so the caller can decide whether to repair.
|
|
2426
|
+
repl_commit(SessionId, Source, Result) :-
|
|
2427
|
+
catch(
|
|
2428
|
+
repl_commit_unsafe(SessionId, Source, Result),
|
|
2429
|
+
CommitError,
|
|
2430
|
+
format(atom(Result), 'error(~w)', [CommitError])
|
|
2431
|
+
).
|
|
2432
|
+
|
|
2433
|
+
repl_commit_unsafe(SessionId, Source, Result) :-
|
|
2434
|
+
guard_not_frozen(SessionId),
|
|
2435
|
+
repl_parse_source(SessionId, Source, Preds, ParseError),
|
|
2436
|
+
repl_commit_from_parse(SessionId, Source, Preds, ParseError, Result).
|
|
2437
|
+
|
|
2438
|
+
repl_commit_from_parse(_SessionId, _Source, _Preds, ParseError, Result) :-
|
|
2439
|
+
ParseError \== none,
|
|
2440
|
+
!,
|
|
2441
|
+
commit_error(ParseError, Result).
|
|
2442
|
+
repl_commit_from_parse(SessionId, Source, Preds, none, Result) :-
|
|
2443
|
+
forall(member(Name/Arity, Preds),
|
|
2444
|
+
catch(abolish(SessionId:Name/Arity), _, true)),
|
|
2445
|
+
load_code(SessionId, Source, LoadError),
|
|
2446
|
+
( LoadError == none
|
|
2447
|
+
-> retractall(session_last_commit(SessionId, _)),
|
|
2448
|
+
assertz(session_last_commit(SessionId, Preds))
|
|
2449
|
+
; true
|
|
2450
|
+
),
|
|
2451
|
+
repl_commit_from_load(LoadError, Result).
|
|
2452
|
+
|
|
2453
|
+
%% repl_last_commit_summary(+SessionId, -Text)
|
|
2454
|
+
%% Rendered "name/arity, name/arity" of the predicates the last commit replaced.
|
|
2455
|
+
repl_last_commit_summary(SessionId, Text) :-
|
|
2456
|
+
( session_last_commit(SessionId, Preds), Preds \= []
|
|
2457
|
+
-> with_output_to(string(Text), print_pred_list(Preds))
|
|
2458
|
+
; Text = ""
|
|
2459
|
+
).
|
|
2460
|
+
|
|
2461
|
+
print_pred_list([]).
|
|
2462
|
+
print_pred_list([Name/Arity]) :-
|
|
2463
|
+
!,
|
|
2464
|
+
format("~w/~w", [Name, Arity]).
|
|
2465
|
+
print_pred_list([Name/Arity|Rest]) :-
|
|
2466
|
+
format("~w/~w, ", [Name, Arity]),
|
|
2467
|
+
print_pred_list(Rest).
|
|
2468
|
+
|
|
2469
|
+
repl_commit_from_load(LoadError, Result) :-
|
|
2470
|
+
LoadError \== none,
|
|
2471
|
+
!,
|
|
2472
|
+
commit_error(LoadError, Result).
|
|
2473
|
+
repl_commit_from_load(none, ok).
|
|
2474
|
+
|
|
2475
|
+
commit_error(Message, Result) :-
|
|
2476
|
+
format(atom(Result), 'error(~w)', [Message]).
|
|
2477
|
+
|
|
2478
|
+
%% repl_execute_frame(+SessionId, +GoalText, -FrameId, -Error)
|
|
2479
|
+
%% Creates a child frame for the meta `execute` tool.
|
|
2480
|
+
repl_execute_frame(SessionId, GoalText, FrameId, Error) :-
|
|
2481
|
+
catch(
|
|
2482
|
+
( read_repl_goal(SessionId, GoalText, Goal, Names),
|
|
2483
|
+
( repl_reserved(Goal)
|
|
2484
|
+
-> throw(error(permission_error(execute, reserved_predicate, Goal),
|
|
2485
|
+
context(repl_execute_frame/4, 'cannot execute a reserved predicate')))
|
|
2486
|
+
; true
|
|
2487
|
+
),
|
|
2488
|
+
repl_initial_state(SessionId, nested, State),
|
|
2489
|
+
create_frame_raw(SessionId, nested, 1,
|
|
2490
|
+
deepclause_mi:repl_solve(Goal, Names, SessionId, State),
|
|
2491
|
+
FrameId),
|
|
2492
|
+
Error = none
|
|
2493
|
+
),
|
|
2494
|
+
ExecuteError,
|
|
2495
|
+
format(atom(Error), '~w', [ExecuteError])
|
|
2496
|
+
).
|
|
2497
|
+
|
|
493
2498
|
%% determine_agent_goal(+SessionId, +Args, -Goal)
|
|
494
2499
|
%% Args is a list of positional arguments - passed directly to agent_main
|
|
495
2500
|
%% Prefers arity that matches the number of arguments provided
|
|
@@ -529,31 +2534,12 @@ determine_agent_goal(SessionId, Args, Goal) :-
|
|
|
529
2534
|
determine_agent_goal(SessionId, _Args, SessionId:agent_main).
|
|
530
2535
|
|
|
531
2536
|
%% step_engine(+SessionId, -Status, -Content, -Payload)
|
|
2537
|
+
%% Legacy single-engine entry point: resolves the session's default frame and
|
|
2538
|
+
%% delegates to step_frame/4. Kept as a thin wrapper so existing callers are
|
|
2539
|
+
%% unaffected by the frame refactor.
|
|
532
2540
|
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
|
-
)
|
|
2541
|
+
( session_default_frame(SessionId, FrameId)
|
|
2542
|
+
-> step_frame(FrameId, Status, Content, Payload)
|
|
557
2543
|
; Status = error,
|
|
558
2544
|
Content = 'No engine found for session',
|
|
559
2545
|
Payload = none
|
|
@@ -561,8 +2547,15 @@ step_engine(SessionId, Status, Content, Payload) :-
|
|
|
561
2547
|
|
|
562
2548
|
%% process_engine_result(+Result, -Status, -Content, -Payload)
|
|
563
2549
|
process_engine_result(output(Text), output, Text, none) :- !.
|
|
2550
|
+
process_engine_result(media(Payload), media, '', Payload) :- is_dict(Payload), !.
|
|
564
2551
|
process_engine_result(log(Text), log, Text, none) :- !.
|
|
565
2552
|
process_engine_result(answer(Text), answer, Text, none) :- !.
|
|
2553
|
+
%% Current 8-arg format (with model spec from with_model/2)
|
|
2554
|
+
process_engine_result(request_agent_loop(Desc, Vars, Tools, Memory, ToolScope, ReasoningEffort, RecipeContext, ModelSpec), request_agent_loop, '',
|
|
2555
|
+
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: ToolScope, reasoningEffort: ReasoningEffort, recipeContext: RecipeContext, modelSpec: ModelSpec}) :- !.
|
|
2556
|
+
%% Prompt-family 6-arg format (tool scope + model spec; no reasoning/recipe)
|
|
2557
|
+
process_engine_result(request_agent_loop(Desc, Vars, Tools, Memory, ToolScope, ModelSpec), request_agent_loop, '',
|
|
2558
|
+
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: ToolScope, reasoningEffort: none, recipeContext: "", modelSpec: ModelSpec}) :- !.
|
|
566
2559
|
%% Note: Memory and tool scope are now passed in the payload for the agent loop
|
|
567
2560
|
process_engine_result(request_agent_loop(Desc, Vars, Tools, Memory, ToolScope, ReasoningEffort, RecipeContext), request_agent_loop, '',
|
|
568
2561
|
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: ToolScope, reasoningEffort: ReasoningEffort, recipeContext: RecipeContext}) :- !.
|
|
@@ -574,14 +2567,25 @@ process_engine_result(request_agent_loop(Desc, Vars, Tools, Memory), request_age
|
|
|
574
2567
|
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: none, reasoningEffort: none, recipeContext: ""}) :- !.
|
|
575
2568
|
process_engine_result(request_exec(Tool, Args), request_exec, '',
|
|
576
2569
|
payload{toolName: Tool, args: Args}) :- !.
|
|
2570
|
+
process_engine_result(request_sample_token(Prompt, Allowed, ModelSpec), request_sample_token, '',
|
|
2571
|
+
payload{prompt: Prompt, allowedTokens: Allowed, modelSpec: ModelSpec}) :- !.
|
|
577
2572
|
process_engine_result(request_sample_token(Prompt, Allowed), request_sample_token, '',
|
|
578
|
-
payload{prompt: Prompt, allowedTokens: Allowed}) :- !.
|
|
2573
|
+
payload{prompt: Prompt, allowedTokens: Allowed, modelSpec: none}) :- !.
|
|
2574
|
+
process_engine_result(request_llm(Messages, ModelSpec), request_llm, '',
|
|
2575
|
+
payload{messages: Messages, modelSpec: ModelSpec}) :- !.
|
|
579
2576
|
process_engine_result(request_llm(Messages), request_llm, '',
|
|
580
|
-
payload{messages: Messages}) :- !.
|
|
2577
|
+
payload{messages: Messages, modelSpec: none}) :- !.
|
|
581
2578
|
process_engine_result(request_judgment(Payload), request_judgment, '', Payload) :- !.
|
|
582
2579
|
%% Tool result from inline tool execution
|
|
583
2580
|
process_engine_result(tool_result(Result), tool_result, '',
|
|
584
2581
|
payload{result: Result}) :- !.
|
|
2582
|
+
%% Goal-mode solution: bindings is a list of Name-RenderedText pairs.
|
|
2583
|
+
process_engine_result(solution(Bindings, Deterministic), solution, '',
|
|
2584
|
+
payload{bindings: Bindings, deterministic: Deterministic}) :- !.
|
|
2585
|
+
%% Prompt-mode meta task result: generated DML source and an optional direct
|
|
2586
|
+
%% answer for simple questions.
|
|
2587
|
+
process_engine_result(meta_result(Code, Answer), meta_result, '',
|
|
2588
|
+
payload{code: Code, answer: Answer}) :- !.
|
|
585
2589
|
process_engine_result(wait_input(Prompt), wait_input, Prompt, none) :- !.
|
|
586
2590
|
process_engine_result(error(Msg), error, Msg, none) :- !.
|
|
587
2591
|
process_engine_result(Other, error, Msg, none) :-
|
|
@@ -589,15 +2593,23 @@ process_engine_result(Other, error, Msg, none) :-
|
|
|
589
2593
|
|
|
590
2594
|
%% destroy_engine(+SessionId)
|
|
591
2595
|
destroy_engine(SessionId) :-
|
|
2596
|
+
% Destroy every frame belonging to this session (the default query frame and
|
|
2597
|
+
% any nested frames still alive). end_frame/1 also clears the default pointer.
|
|
2598
|
+
( findall(FrameId, session_frame(FrameId, SessionId, _, _), FrameIds)
|
|
2599
|
+
-> forall(member(FrameId, FrameIds), end_frame(FrameId))
|
|
2600
|
+
; true
|
|
2601
|
+
),
|
|
592
2602
|
( session_engine(SessionId, Engine)
|
|
593
2603
|
-> catch(engine_destroy(Engine), _, true)
|
|
594
2604
|
; true
|
|
595
2605
|
),
|
|
2606
|
+
retractall(session_default_frame(SessionId, _)),
|
|
596
2607
|
retractall(session_engine(SessionId, _)),
|
|
597
2608
|
retractall(session_params(SessionId, _)),
|
|
598
2609
|
retractall(session_user_tools(SessionId, _)),
|
|
599
2610
|
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
600
2611
|
retractall(session_pending_input(SessionId, _)),
|
|
2612
|
+
retractall(session_input_cancelled(SessionId)),
|
|
601
2613
|
retractall(session_agent_result(SessionId, _)),
|
|
602
2614
|
retractall(session_exec_result(SessionId, _)),
|
|
603
2615
|
retractall(session_sample_token_result(SessionId, _)),
|
|
@@ -671,6 +2683,13 @@ provide_input(SessionId, Input) :-
|
|
|
671
2683
|
assertz(session_pending_input(SessionId, Input)),
|
|
672
2684
|
post_signal_to_engine(SessionId, input_provided).
|
|
673
2685
|
|
|
2686
|
+
%% provide_input_cancelled(+SessionId)
|
|
2687
|
+
%% Non-interactive runs have no human. Signal the waiting input/2 to fail
|
|
2688
|
+
%% instead of blocking.
|
|
2689
|
+
provide_input_cancelled(SessionId) :-
|
|
2690
|
+
assertz(session_input_cancelled(SessionId)),
|
|
2691
|
+
post_signal_to_engine(SessionId, input_provided).
|
|
2692
|
+
|
|
674
2693
|
%% post_signal_to_engine(+SessionId, +Signal)
|
|
675
2694
|
%% Posts a signal via dynamic predicate (most reliable for WASM engines)
|
|
676
2695
|
%% Falls back to engine_post only if dynamic assert fails
|
|
@@ -790,6 +2809,25 @@ get_reasoning_effort(State, Effort) :-
|
|
|
790
2809
|
; Effort = none
|
|
791
2810
|
).
|
|
792
2811
|
|
|
2812
|
+
%% set_model_state(+StateIn, +Spec, -StateOut)
|
|
2813
|
+
set_model_state(StateIn, Spec, StateOut) :-
|
|
2814
|
+
StateOut = StateIn.put(model_spec, Spec).
|
|
2815
|
+
|
|
2816
|
+
%% clear_model_state(+StateIn, -StateOut)
|
|
2817
|
+
clear_model_state(StateIn, StateOut) :-
|
|
2818
|
+
( get_dict(model_spec, StateIn, _)
|
|
2819
|
+
-> del_dict(model_spec, StateIn, _, StateOut)
|
|
2820
|
+
; StateOut = StateIn
|
|
2821
|
+
).
|
|
2822
|
+
|
|
2823
|
+
%% get_model_spec(+State, -Spec)
|
|
2824
|
+
%% `none` means "no override" (use the session model).
|
|
2825
|
+
get_model_spec(State, Spec) :-
|
|
2826
|
+
( get_dict(model_spec, State, Spec)
|
|
2827
|
+
-> true
|
|
2828
|
+
; Spec = none
|
|
2829
|
+
).
|
|
2830
|
+
|
|
793
2831
|
%% ============================================================
|
|
794
2832
|
%% Recipe Context Helpers
|
|
795
2833
|
%% ============================================================
|
|
@@ -1166,8 +3204,9 @@ mi_call(task(Desc), StateIn, StateOut) :-
|
|
|
1166
3204
|
get_tool_scope(StateIn, ToolScope),
|
|
1167
3205
|
get_reasoning_effort(StateIn, ReasoningEffort),
|
|
1168
3206
|
get_recipe_context(StateIn, RecipeContext),
|
|
3207
|
+
get_model_spec(StateIn, ModelSpec),
|
|
1169
3208
|
% Yield request with current memory and tool scope
|
|
1170
|
-
engine_yield(request_agent_loop(InterpDesc, [], UserTools, Memory, ToolScope, ReasoningEffort, RecipeContext)),
|
|
3209
|
+
engine_yield(request_agent_loop(InterpDesc, [], UserTools, Memory, ToolScope, ReasoningEffort, RecipeContext, ModelSpec)),
|
|
1171
3210
|
% Handle signals (tool calls) until agent_done
|
|
1172
3211
|
handle_agent_signals(SessionId, StateIn, StateAfterAgent),
|
|
1173
3212
|
session_agent_result(SessionId, Result),
|
|
@@ -1237,7 +3276,8 @@ mi_call(prompt(Desc), StateIn, StateOut) :-
|
|
|
1237
3276
|
get_depth(StateIn, Depth),
|
|
1238
3277
|
add_trace_entry(SessionId, llm_call, prompt, [InterpDesc], Depth),
|
|
1239
3278
|
% prompt/N is a simple LLM call - no tools, no tool scope
|
|
1240
|
-
|
|
3279
|
+
get_model_spec(StateIn, ModelSpec),
|
|
3280
|
+
engine_yield(request_agent_loop(InterpDesc, [], [], [], none, ModelSpec)),
|
|
1241
3281
|
% Wait for agent_done (no tool calls expected since no tools provided)
|
|
1242
3282
|
handle_agent_signals(SessionId, StateIn, StateOut),
|
|
1243
3283
|
session_agent_result(SessionId, Result),
|
|
@@ -1272,7 +3312,8 @@ mi_call_prompt_n(Desc, Vars, VarNames, StateIn, StateOut) :-
|
|
|
1272
3312
|
get_depth(StateIn, Depth),
|
|
1273
3313
|
add_trace_entry(SessionId, llm_call, prompt, [InterpDesc], Depth),
|
|
1274
3314
|
% prompt/N is a simple LLM call - no tools, no tool scope
|
|
1275
|
-
|
|
3315
|
+
get_model_spec(StateIn, ModelSpec),
|
|
3316
|
+
engine_yield(request_agent_loop(InterpDesc, VarNames, [], [], none, ModelSpec)),
|
|
1276
3317
|
% Wait for agent_done (no tool calls expected since no tools provided)
|
|
1277
3318
|
handle_agent_signals(SessionId, StateIn, StateOut),
|
|
1278
3319
|
session_agent_result(SessionId, Result),
|
|
@@ -1299,7 +3340,8 @@ mi_call_sample_token(Desc, Allowed, Token, StateIn, StateOut) :-
|
|
|
1299
3340
|
get_session_id(SessionId),
|
|
1300
3341
|
get_depth(StateIn, Depth),
|
|
1301
3342
|
add_trace_entry(SessionId, llm_call, sample_token, [InterpDesc, Allowed], Depth),
|
|
1302
|
-
|
|
3343
|
+
get_model_spec(StateIn, ModelSpec),
|
|
3344
|
+
engine_yield(request_sample_token(InterpDesc, Allowed, ModelSpec)),
|
|
1303
3345
|
wait_sample_token_signal(SessionId),
|
|
1304
3346
|
( session_sample_token_result(SessionId, Result)
|
|
1305
3347
|
-> retract(session_sample_token_result(SessionId, _)),
|
|
@@ -1507,7 +3549,8 @@ mi_call_llm(Messages, Reply, StateIn, StateOut) :-
|
|
|
1507
3549
|
get_session_id(SessionId),
|
|
1508
3550
|
get_depth(StateIn, Depth),
|
|
1509
3551
|
add_trace_entry(SessionId, llm_call, llm, [Messages], Depth),
|
|
1510
|
-
|
|
3552
|
+
get_model_spec(StateIn, ModelSpec),
|
|
3553
|
+
engine_yield(request_llm(Messages, ModelSpec)),
|
|
1511
3554
|
wait_llm_signal(SessionId),
|
|
1512
3555
|
( session_llm_result(SessionId, Result)
|
|
1513
3556
|
-> retract(session_llm_result(SessionId, _)),
|
|
@@ -1550,7 +3593,8 @@ mi_call_task_n(Desc, Vars, VarNames, StateIn, StateOut) :-
|
|
|
1550
3593
|
get_tool_scope(StateIn, ToolScope),
|
|
1551
3594
|
get_reasoning_effort(StateIn, ReasoningEffort),
|
|
1552
3595
|
get_recipe_context(StateIn, RecipeContext),
|
|
1553
|
-
|
|
3596
|
+
get_model_spec(StateIn, ModelSpec),
|
|
3597
|
+
engine_yield(request_agent_loop(InterpDesc, VarNames, UserTools, Memory, ToolScope, ReasoningEffort, RecipeContext, ModelSpec)),
|
|
1554
3598
|
% Handle signals (tool calls) until agent_done
|
|
1555
3599
|
handle_agent_signals(SessionId, StateIn, StateAfterAgent),
|
|
1556
3600
|
session_agent_result(SessionId, Result),
|
|
@@ -1665,6 +3709,17 @@ mi_call(with_reasoning(Goal, Effort), StateIn, StateOut) :-
|
|
|
1665
3709
|
mi_call(Goal, ScopedState, StateWithScope),
|
|
1666
3710
|
clear_reasoning_effort_state(StateWithScope, StateOut).
|
|
1667
3711
|
|
|
3712
|
+
%% mi_call(with_model(Spec, Goal), +StateIn, -StateOut)
|
|
3713
|
+
%% Run Goal with a specific model, and optionally a reasoning level. `Spec` is a
|
|
3714
|
+
%% string — "provider:model", "provider:model#reasoning", "model",
|
|
3715
|
+
%% "model#reasoning" — or a model{provider, id, temperature, maxTokens, reasoning}
|
|
3716
|
+
%% dict. It applies to task/N, prompt/N and sample_token/N (not judgments).
|
|
3717
|
+
mi_call(with_model(Spec, Goal), StateIn, StateOut) :-
|
|
3718
|
+
!,
|
|
3719
|
+
set_model_state(StateIn, Spec, ScopedState),
|
|
3720
|
+
mi_call(Goal, ScopedState, StateWithScope),
|
|
3721
|
+
clear_model_state(StateWithScope, StateOut).
|
|
3722
|
+
|
|
1668
3723
|
%% mi_call(with_recipe(Goal, Query), +StateIn, -StateOut)
|
|
1669
3724
|
%% Run Goal with a recipe loaded as transient context (not persisted to memory).
|
|
1670
3725
|
mi_call(with_recipe(Goal, Query), StateIn, StateOut) :-
|
|
@@ -1697,6 +3752,80 @@ mi_call(get_memory(Messages), StateIn, StateIn) :-
|
|
|
1697
3752
|
!,
|
|
1698
3753
|
get_memory(StateIn, Messages).
|
|
1699
3754
|
|
|
3755
|
+
%% ============================================================
|
|
3756
|
+
%% Media (see docs/DML_MEDIA.md)
|
|
3757
|
+
%% ============================================================
|
|
3758
|
+
|
|
3759
|
+
%% mi_call(read_media(+Path, -Media), +StateIn, -StateOut)
|
|
3760
|
+
%% Binds Media = media(Path, Mime) for a workspace-relative image. The bytes
|
|
3761
|
+
%% stay on disk; only the reference enters memory.
|
|
3762
|
+
mi_call(read_media(Path, Media), StateIn, StateIn) :-
|
|
3763
|
+
!,
|
|
3764
|
+
get_params(StateIn, Params),
|
|
3765
|
+
interpolate_desc(Path, Params, P),
|
|
3766
|
+
media_mime(P, Mime),
|
|
3767
|
+
Media = media(P, Mime).
|
|
3768
|
+
|
|
3769
|
+
%% mi_call(attach_media(+Media), +StateIn, -StateOut)
|
|
3770
|
+
%% Appends a user memory message carrying a reference-only media entry, so the
|
|
3771
|
+
%% next task/N can see the image.
|
|
3772
|
+
mi_call(attach_media(Media), StateIn, StateOut) :-
|
|
3773
|
+
!,
|
|
3774
|
+
media_dict(Media, media{path: Path, mime: Mime}),
|
|
3775
|
+
format(atom(Note), '[attached ~w: ~w]', [Mime, Path]),
|
|
3776
|
+
Message = message{role: user, content: Note, media: [media{path: Path, mime: Mime}]},
|
|
3777
|
+
get_memory(StateIn, Memory),
|
|
3778
|
+
append(Memory, [Message], NewMemory),
|
|
3779
|
+
set_memory(StateIn, NewMemory, StateOut).
|
|
3780
|
+
|
|
3781
|
+
%% mi_call(save_image(+Media, +OutPath), +StateIn, -StateOut)
|
|
3782
|
+
%% Write a media reference to a workspace file: `save_image(media(Path, Mime), "out.png")`,
|
|
3783
|
+
%% `save_image(media{svg: Svg}, "out.svg")`. Parent directories must exist.
|
|
3784
|
+
mi_call(save_image(Media, Out), StateIn, StateIn) :-
|
|
3785
|
+
!,
|
|
3786
|
+
media_dict(Media, Dict),
|
|
3787
|
+
( get_dict(kind, Dict, inline_svg),
|
|
3788
|
+
get_dict(data, Dict, Svg)
|
|
3789
|
+
-> resolve_workspace_path(Out, OutAbs),
|
|
3790
|
+
open(OutAbs, write, Stream),
|
|
3791
|
+
write(Stream, Svg),
|
|
3792
|
+
close(Stream)
|
|
3793
|
+
; get_dict(path, Dict, Path),
|
|
3794
|
+
Path \== ""
|
|
3795
|
+
-> resolve_workspace_path(Path, InAbs),
|
|
3796
|
+
resolve_workspace_path(Out, OutAbs),
|
|
3797
|
+
copy_file(InAbs, OutAbs)
|
|
3798
|
+
; throw(error(domain_error(save_image, Media), save_image/2))
|
|
3799
|
+
).
|
|
3800
|
+
|
|
3801
|
+
%% media_dict(+Media, -Dict)
|
|
3802
|
+
%% Normalizes an image reference for display/attachment:
|
|
3803
|
+
%% media(Path, Mime) | media(Path) | a bare Path string
|
|
3804
|
+
%% media{path: Path} | media{path: Path, mime: Mime}
|
|
3805
|
+
%% media{svg: SvgString} (inline SVG)
|
|
3806
|
+
%% media{data: Base64} | media{data: Base64, mime: Mime} (inline bytes)
|
|
3807
|
+
media_dict(media(Path, Mime), media{path: Path, mime: Mime}) :- !.
|
|
3808
|
+
media_dict(media(Path), media{path: Path, mime: Mime}) :- !, media_mime(Path, Mime).
|
|
3809
|
+
media_dict(media{path: Path, mime: Mime}, media{path: Path, mime: Mime}) :- !.
|
|
3810
|
+
media_dict(media{path: Path}, media{path: Path, mime: Mime}) :- !, media_mime(Path, Mime).
|
|
3811
|
+
media_dict(media{svg: Svg}, media{path: "", mime: "image/svg+xml", kind: inline_svg, data: Svg}) :- !.
|
|
3812
|
+
media_dict(media{data: Data, mime: Mime}, media{path: "", mime: Mime, kind: inline_data, data: Data}) :- !.
|
|
3813
|
+
media_dict(media{data: Data}, media{path: "", mime: "image/png", kind: inline_data, data: Data}) :- !.
|
|
3814
|
+
media_dict(Path, media{path: Path, mime: Mime}) :- media_mime(Path, Mime).
|
|
3815
|
+
|
|
3816
|
+
%% media_mime(+Path, -Mime)
|
|
3817
|
+
%% Allowed media types, matching pi image content blocks.
|
|
3818
|
+
media_mime(Path, Mime) :-
|
|
3819
|
+
( string(Path) -> atom_string(Atom, Path) ; Atom = Path ),
|
|
3820
|
+
( sub_atom(Atom, _, _, 0, '.png') -> Mime = "image/png"
|
|
3821
|
+
; sub_atom(Atom, _, _, 0, '.svg') -> Mime = "image/svg+xml"
|
|
3822
|
+
; sub_atom(Atom, _, _, 0, '.jpg') -> Mime = "image/jpeg"
|
|
3823
|
+
; sub_atom(Atom, _, _, 0, '.jpeg') -> Mime = "image/jpeg"
|
|
3824
|
+
; sub_atom(Atom, _, _, 0, '.gif') -> Mime = "image/gif"
|
|
3825
|
+
; sub_atom(Atom, _, _, 0, '.webp') -> Mime = "image/webp"
|
|
3826
|
+
; fail
|
|
3827
|
+
).
|
|
3828
|
+
|
|
1700
3829
|
%% ============================================================
|
|
1701
3830
|
%% Output Predicates
|
|
1702
3831
|
%% ============================================================
|
|
@@ -1709,6 +3838,29 @@ mi_call(answer(Text), StateIn, StateIn) :-
|
|
|
1709
3838
|
engine_yield(answer(InterpText)),
|
|
1710
3839
|
throw('$answer_commit').
|
|
1711
3840
|
|
|
3841
|
+
%% mi_call(output(Media), +StateIn, -StateOut)
|
|
3842
|
+
%% Present an image instead of text: `output(media(Path, Mime))`, `output(media(Path))`,
|
|
3843
|
+
%% or a dict form (`media{svg: Svg}`, `media{data: Base64, mime: Mime}`). The host
|
|
3844
|
+
%% renders it inline (kitty/iTerm2) or reports the path.
|
|
3845
|
+
%% This clause must come before the generic `output(Text)` clause; it fails
|
|
3846
|
+
%% through for anything that is not a media reference.
|
|
3847
|
+
mi_call(output(Media), StateIn, StateIn) :-
|
|
3848
|
+
nonvar(Media),
|
|
3849
|
+
( Media = media(_, _)
|
|
3850
|
+
; Media = media(_)
|
|
3851
|
+
; is_dict(Media)
|
|
3852
|
+
),
|
|
3853
|
+
media_dict(Media, Dict),
|
|
3854
|
+
!,
|
|
3855
|
+
get_dict(mime, Dict, Mime),
|
|
3856
|
+
get_dict(path, Dict, Path),
|
|
3857
|
+
( get_dict(data, Dict, Data) -> true ; Data = "" ),
|
|
3858
|
+
( get_dict(kind, Dict, Kind) -> true ; Kind = file ),
|
|
3859
|
+
get_session_id(SessionId),
|
|
3860
|
+
get_depth(StateIn, Depth),
|
|
3861
|
+
add_trace_entry(SessionId, media, media, [Path], Depth),
|
|
3862
|
+
engine_yield(media(media{path: Path, mimeType: Mime, kind: Kind, data: Data})).
|
|
3863
|
+
|
|
1712
3864
|
%% mi_call(output(Text), +StateIn, -StateOut)
|
|
1713
3865
|
mi_call(output(Text), StateIn, StateIn) :-
|
|
1714
3866
|
get_params(StateIn, Params),
|
|
@@ -1729,8 +3881,11 @@ mi_call(input(Prompt, Input), StateIn, StateIn) :-
|
|
|
1729
3881
|
engine_yield(wait_input(InterpPrompt)),
|
|
1730
3882
|
% Use get_next_signal helper which handles both dynamic predicate and engine_fetch
|
|
1731
3883
|
get_next_signal(SessionId, _Signal),
|
|
1732
|
-
%
|
|
1733
|
-
(
|
|
3884
|
+
% A cancelled input request fails the goal (non-interactive policy).
|
|
3885
|
+
( session_input_cancelled(SessionId)
|
|
3886
|
+
-> retractall(session_input_cancelled(SessionId)),
|
|
3887
|
+
fail
|
|
3888
|
+
; session_pending_input(SessionId, Input)
|
|
1734
3889
|
-> retract(session_pending_input(SessionId, Input)),
|
|
1735
3890
|
add_trace_with_result(SessionId, exit, input, [InterpPrompt], Input, Depth)
|
|
1736
3891
|
; Input = "", % Default to empty if no input provided
|
|
@@ -1752,6 +3907,27 @@ mi_call(log(Text), StateIn, StateIn) :-
|
|
|
1752
3907
|
interpolate_desc(Text, Params, InterpText),
|
|
1753
3908
|
engine_yield(log(InterpText)).
|
|
1754
3909
|
|
|
3910
|
+
%% mi_call(format(Format, Args), +StateIn, -StateOut)
|
|
3911
|
+
%% In REPL mode, capture format/2 output as an output event rather than
|
|
3912
|
+
%% writing to the process stdout. Outside the REPL (runDML), keep normal
|
|
3913
|
+
%% behavior so existing traces/prompts are unaffected.
|
|
3914
|
+
mi_call(format(Format, Args), StateIn, StateIn) :-
|
|
3915
|
+
get_session_id(SessionId),
|
|
3916
|
+
( session_repl_mode(SessionId, true)
|
|
3917
|
+
-> with_output_to(string(Text), format(Format, Args)),
|
|
3918
|
+
mi_call(output(Text), StateIn, StateIn)
|
|
3919
|
+
; format(Format, Args)
|
|
3920
|
+
).
|
|
3921
|
+
|
|
3922
|
+
%% mi_call(writeln(Value), +StateIn, -StateOut)
|
|
3923
|
+
mi_call(writeln(Value), StateIn, StateIn) :-
|
|
3924
|
+
get_session_id(SessionId),
|
|
3925
|
+
( session_repl_mode(SessionId, true)
|
|
3926
|
+
-> with_output_to(string(Text), writeln(Value)),
|
|
3927
|
+
mi_call(output(Text), StateIn, StateIn)
|
|
3928
|
+
; writeln(Value)
|
|
3929
|
+
).
|
|
3930
|
+
|
|
1755
3931
|
%% ============================================================
|
|
1756
3932
|
%% Context Stack Management - for memory save/restore
|
|
1757
3933
|
%% ============================================================
|
|
@@ -1878,20 +4054,57 @@ mi_call(false, _StateIn, _StateOut) :-
|
|
|
1878
4054
|
%% mi_call(retractall(Head), +StateIn, -StateOut)
|
|
1879
4055
|
%% Database predicates must operate on the session module so DML-side facts
|
|
1880
4056
|
%% (draft/4, claim/3, ...) are asserted/retracted in the right module.
|
|
4057
|
+
%% repl_reserved_clause(+Term)
|
|
4058
|
+
%% True when Term defines a reserved predicate. directives and the legitimate
|
|
4059
|
+
%% repl_delete harness form are exempt.
|
|
4060
|
+
repl_reserved_clause((:- _)) :-
|
|
4061
|
+
!,
|
|
4062
|
+
fail.
|
|
4063
|
+
repl_reserved_clause(repl_delete(_)) :-
|
|
4064
|
+
!,
|
|
4065
|
+
fail.
|
|
4066
|
+
repl_reserved_clause((tool(ToolHead, _) :- _)) :-
|
|
4067
|
+
!,
|
|
4068
|
+
functor(ToolHead, Name, _),
|
|
4069
|
+
repl_reserved_name(Name).
|
|
4070
|
+
repl_reserved_clause((tool(ToolHead) :- _)) :-
|
|
4071
|
+
!,
|
|
4072
|
+
functor(ToolHead, Name, _),
|
|
4073
|
+
repl_reserved_name(Name).
|
|
4074
|
+
repl_reserved_clause((Head :- _)) :-
|
|
4075
|
+
functor(Head, Name, _),
|
|
4076
|
+
repl_protected_name(Name).
|
|
4077
|
+
repl_reserved_clause(Head) :-
|
|
4078
|
+
functor(Head, Name, _),
|
|
4079
|
+
repl_protected_name(Name).
|
|
4080
|
+
|
|
1881
4081
|
mi_call(assertz(Head), StateIn, StateIn) :-
|
|
1882
4082
|
get_session_id(SessionId),
|
|
4083
|
+
guard_not_frozen(SessionId),
|
|
4084
|
+
guard_reserved_clause(Head),
|
|
1883
4085
|
assertz(SessionId:Head).
|
|
1884
4086
|
|
|
1885
4087
|
mi_call(asserta(Head), StateIn, StateIn) :-
|
|
1886
4088
|
get_session_id(SessionId),
|
|
4089
|
+
guard_not_frozen(SessionId),
|
|
4090
|
+
guard_reserved_clause(Head),
|
|
1887
4091
|
asserta(SessionId:Head).
|
|
1888
4092
|
|
|
4093
|
+
guard_reserved_clause(Head) :-
|
|
4094
|
+
( repl_reserved_clause(Head)
|
|
4095
|
+
-> throw(error(permission_error(modify, reserved_name, Head),
|
|
4096
|
+
context(mi_call/3, 'cannot define a reserved predicate')))
|
|
4097
|
+
; true
|
|
4098
|
+
).
|
|
4099
|
+
|
|
1889
4100
|
mi_call(retract(Head), StateIn, StateIn) :-
|
|
1890
4101
|
get_session_id(SessionId),
|
|
4102
|
+
guard_not_frozen(SessionId),
|
|
1891
4103
|
retract(SessionId:Head).
|
|
1892
4104
|
|
|
1893
4105
|
mi_call(retractall(Head), StateIn, StateIn) :-
|
|
1894
4106
|
get_session_id(SessionId),
|
|
4107
|
+
guard_not_frozen(SessionId),
|
|
1895
4108
|
retractall(SessionId:Head).
|
|
1896
4109
|
|
|
1897
4110
|
%% ============================================================
|
|
@@ -2100,6 +4313,7 @@ resolve_workspace_path(RelPath, FullPath) :-
|
|
|
2100
4313
|
mi_call(consult(File), StateIn, StateIn) :-
|
|
2101
4314
|
!,
|
|
2102
4315
|
get_session_id(SessionId),
|
|
4316
|
+
guard_not_frozen(SessionId),
|
|
2103
4317
|
resolve_workspace_path(File, FullPath),
|
|
2104
4318
|
readutil:read_file_to_string(FullPath, Code, []),
|
|
2105
4319
|
consult_string(Code, SessionId).
|
|
@@ -2146,7 +4360,14 @@ consult_clauses(Stream, SessionId) :-
|
|
|
2146
4360
|
).
|
|
2147
4361
|
|
|
2148
4362
|
%% consult_process_term(+Term, +SessionId)
|
|
2149
|
-
%% Process a single term read during consult
|
|
4363
|
+
%% Process a single term read during consult. Reserved-name definitions are
|
|
4364
|
+
%% refused so consulted files cannot shadow the REPL control surface.
|
|
4365
|
+
consult_process_term(Term, _SessionId) :-
|
|
4366
|
+
repl_reserved_clause(Term),
|
|
4367
|
+
!,
|
|
4368
|
+
throw(error(permission_error(modify, reserved_name, Term),
|
|
4369
|
+
context(consult_process_term/2, 'reserved predicate'))).
|
|
4370
|
+
|
|
2150
4371
|
consult_process_term(:-(Directive), SessionId) :-
|
|
2151
4372
|
% Handle directives - route through mi_call so custom predicates like consult/1 work
|
|
2152
4373
|
!,
|
|
@@ -2165,7 +4386,7 @@ consult_process_term((tool(ToolHead, Description) :- Body), SessionId) :-
|
|
|
2165
4386
|
extract_tool_schema(ToolHead, Description, ToolName, Schema),
|
|
2166
4387
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
2167
4388
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
2168
|
-
|
|
4389
|
+
format_tool_source(ToolHead, Description, Body, SourceCode),
|
|
2169
4390
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
2170
4391
|
assertz(SessionId:(tool(ToolHead) :- Body)).
|
|
2171
4392
|
|
|
@@ -2175,7 +4396,7 @@ consult_process_term((tool(ToolHead) :- Body), SessionId) :-
|
|
|
2175
4396
|
extract_tool_schema(ToolHead, none, ToolName, Schema),
|
|
2176
4397
|
assertz(session_user_tools(SessionId, ToolName)),
|
|
2177
4398
|
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
2178
|
-
|
|
4399
|
+
format_tool_source(ToolHead, none, Body, SourceCode),
|
|
2179
4400
|
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
2180
4401
|
assertz(SessionId:(tool(ToolHead) :- Body)).
|
|
2181
4402
|
|
|
@@ -2284,6 +4505,22 @@ mi_call(delete_directory(Dir), StateIn, StateIn) :-
|
|
|
2284
4505
|
resolve_workspace_path(Dir, FullPath),
|
|
2285
4506
|
delete_directory(FullPath).
|
|
2286
4507
|
|
|
4508
|
+
%% mi_call(size_file(File, Size), +StateIn, -StateOut)
|
|
4509
|
+
%% size_file/2 is a plain SWI builtin, so it would otherwise resolve `File`
|
|
4510
|
+
%% against the WASM process cwd (not the workspace) and demand `/workspace/...`.
|
|
4511
|
+
mi_call(size_file(File, Size), StateIn, StateIn) :-
|
|
4512
|
+
!,
|
|
4513
|
+
resolve_workspace_path(File, FullPath),
|
|
4514
|
+
size_file(FullPath, Size).
|
|
4515
|
+
|
|
4516
|
+
%% mi_call(time_file(File, Time), +StateIn, -StateOut)
|
|
4517
|
+
%% Modifications time (seconds since epoch); same workspace-relative rule as
|
|
4518
|
+
%% size_file/2.
|
|
4519
|
+
mi_call(time_file(File, Time), StateIn, StateIn) :-
|
|
4520
|
+
!,
|
|
4521
|
+
resolve_workspace_path(File, FullPath),
|
|
4522
|
+
time_file(FullPath, Time).
|
|
4523
|
+
|
|
2287
4524
|
%% ============================================================
|
|
2288
4525
|
%% Catch/Throw - STATE THREADED
|
|
2289
4526
|
%% ============================================================
|
|
@@ -2365,6 +4602,9 @@ is_mi_special_predicate(yield(_)).
|
|
|
2365
4602
|
is_mi_special_predicate(log(_)).
|
|
2366
4603
|
is_mi_special_predicate(system(_)).
|
|
2367
4604
|
is_mi_special_predicate(user(_)). %% Add user message to context
|
|
4605
|
+
is_mi_special_predicate(read_media(_,_)).
|
|
4606
|
+
is_mi_special_predicate(attach_media(_)).
|
|
4607
|
+
is_mi_special_predicate(save_image(_,_)).
|
|
2368
4608
|
is_mi_special_predicate(task(_)).
|
|
2369
4609
|
is_mi_special_predicate(task(_,_)).
|
|
2370
4610
|
is_mi_special_predicate(task(_,_,_)).
|
|
@@ -2388,6 +4628,7 @@ is_mi_special_predicate(with_tools(_,_)).
|
|
|
2388
4628
|
is_mi_special_predicate(without_tools(_,_)).
|
|
2389
4629
|
%% Reasoning effort scoping
|
|
2390
4630
|
is_mi_special_predicate(with_reasoning(_,_)).
|
|
4631
|
+
is_mi_special_predicate(with_model(_,_)).
|
|
2391
4632
|
%% Recipe context scoping
|
|
2392
4633
|
is_mi_special_predicate(with_recipe(_,_)).
|
|
2393
4634
|
%% Semantic judgment predicates
|
|
@@ -2402,6 +4643,16 @@ is_mi_special_predicate(with_judgment(_,_)).
|
|
|
2402
4643
|
is_mi_special_predicate(require_judgment(_,_)).
|
|
2403
4644
|
%% Context stack management
|
|
2404
4645
|
is_mi_special_predicate(get_memory(_)).
|
|
4646
|
+
is_mi_special_predicate(format(_,_)).
|
|
4647
|
+
is_mi_special_predicate(writeln(_)).
|
|
4648
|
+
is_mi_special_predicate(input(_,_)).
|
|
4649
|
+
is_mi_special_predicate(call(_)).
|
|
4650
|
+
is_mi_special_predicate(call(_,_)).
|
|
4651
|
+
is_mi_special_predicate(call(_,_,_)).
|
|
4652
|
+
is_mi_special_predicate(call(_,_,_,_)).
|
|
4653
|
+
is_mi_special_predicate(once(_)).
|
|
4654
|
+
is_mi_special_predicate(ignore(_)).
|
|
4655
|
+
is_mi_special_predicate(not(_)).
|
|
2405
4656
|
is_mi_special_predicate(push_context).
|
|
2406
4657
|
is_mi_special_predicate(push_context(_)).
|
|
2407
4658
|
is_mi_special_predicate(pop_context).
|
|
@@ -2459,6 +4710,8 @@ is_mi_special_predicate(close(_)).
|
|
|
2459
4710
|
is_mi_special_predicate(read_string(_,_,_)).
|
|
2460
4711
|
is_mi_special_predicate(write(_,_)).
|
|
2461
4712
|
is_mi_special_predicate(exists_file(_)).
|
|
4713
|
+
is_mi_special_predicate(size_file(_,_)).
|
|
4714
|
+
is_mi_special_predicate(time_file(_,_)).
|
|
2462
4715
|
is_mi_special_predicate(exists_directory(_)).
|
|
2463
4716
|
is_mi_special_predicate(directory_files(_,_)).
|
|
2464
4717
|
is_mi_special_predicate(make_directory(_)).
|
|
@@ -2472,6 +4725,105 @@ is_mi_special_predicate(use_module(_,_)).
|
|
|
2472
4725
|
is_mi_special_predicate([_]).
|
|
2473
4726
|
is_mi_special_predicate([_|_]).
|
|
2474
4727
|
|
|
4728
|
+
%% mi_call(call(Goal), +StateIn, -StateOut)
|
|
4729
|
+
%% Route call/1 through the meta-interpreter so backtracking and the
|
|
4730
|
+
%% reserved-predicate guard apply (a bare call/repl_* would bypass them).
|
|
4731
|
+
mi_call(call(Goal), StateIn, StateOut) :-
|
|
4732
|
+
guard_reserved_goal(Goal, StateIn),
|
|
4733
|
+
!,
|
|
4734
|
+
mi_call(Goal, StateIn, StateOut).
|
|
4735
|
+
|
|
4736
|
+
%% mi_call(call(Closure, Extra...), +StateIn, -StateOut)
|
|
4737
|
+
%% call/2..4 must also route through the meta-interpreter; otherwise a wrapped
|
|
4738
|
+
%% task/prompt/exec runs in raw Prolog, where task_named/3 does not exist.
|
|
4739
|
+
mi_call(call(Goal, A), StateIn, StateOut) :-
|
|
4740
|
+
!,
|
|
4741
|
+
extend_callable(Goal, [A], Callable),
|
|
4742
|
+
mi_call(call(Callable), StateIn, StateOut).
|
|
4743
|
+
|
|
4744
|
+
mi_call(call(Goal, A, B), StateIn, StateOut) :-
|
|
4745
|
+
!,
|
|
4746
|
+
extend_callable(Goal, [A, B], Callable),
|
|
4747
|
+
mi_call(call(Callable), StateIn, StateOut).
|
|
4748
|
+
|
|
4749
|
+
mi_call(call(Goal, A, B, C), StateIn, StateOut) :-
|
|
4750
|
+
!,
|
|
4751
|
+
extend_callable(Goal, [A, B, C], Callable),
|
|
4752
|
+
mi_call(call(Callable), StateIn, StateOut).
|
|
4753
|
+
|
|
4754
|
+
%% extend_callable(+Closure, +ExtraArgs, -Callable)
|
|
4755
|
+
extend_callable(Closure, Extra, Callable) :-
|
|
4756
|
+
Closure =.. List,
|
|
4757
|
+
append(List, Extra, Extended),
|
|
4758
|
+
Callable =.. Extended.
|
|
4759
|
+
|
|
4760
|
+
%% mi_call(once(Goal), +StateIn, -StateOut)
|
|
4761
|
+
%% once/1 must route through the meta-interpreter. As a plain builtin it would
|
|
4762
|
+
%% run Goal in raw Prolog, so a wrapped task/prompt/exec fails with
|
|
4763
|
+
%% task_named/3 undefined. Commits to the first solution of Goal, like once/1.
|
|
4764
|
+
mi_call(once(Goal), StateIn, StateOut) :-
|
|
4765
|
+
!,
|
|
4766
|
+
once(mi_call(Goal, StateIn, StateOut)).
|
|
4767
|
+
|
|
4768
|
+
%% mi_call(ignore(Goal), +StateIn, -StateOut)
|
|
4769
|
+
mi_call(ignore(Goal), StateIn, StateOut) :-
|
|
4770
|
+
!,
|
|
4771
|
+
( once(mi_call(Goal, StateIn, StateOut0))
|
|
4772
|
+
-> StateOut = StateOut0
|
|
4773
|
+
; StateOut = StateIn
|
|
4774
|
+
).
|
|
4775
|
+
|
|
4776
|
+
%% mi_call(not(Goal), +StateIn, -StateOut) (alias for \+/1)
|
|
4777
|
+
mi_call(not(Goal), StateIn, StateIn) :-
|
|
4778
|
+
!,
|
|
4779
|
+
mi_call(\+(Goal), StateIn, StateIn).
|
|
4780
|
+
|
|
4781
|
+
%% Refuse reserved predicates invoked directly in non-meta frames. This is the
|
|
4782
|
+
%% second of the three-place reserved guard (execute, mi_call, assert/load).
|
|
4783
|
+
mi_call(Goal, StateIn, _StateOut) :-
|
|
4784
|
+
guard_reserved_direct(Goal, StateIn),
|
|
4785
|
+
!,
|
|
4786
|
+
throw(error(permission_error(execute, reserved_predicate, Goal),
|
|
4787
|
+
context(mi_call/3, 'cannot invoke a reserved REPL predicate'))).
|
|
4788
|
+
|
|
4789
|
+
%% guard_reserved_goal(+Goal, +StateIn)
|
|
4790
|
+
%% Throws when Goal names a reserved predicate and the frame is not meta.
|
|
4791
|
+
guard_reserved_goal(Goal, StateIn) :-
|
|
4792
|
+
( is_dict(StateIn), get_dict(frame_kind, StateIn, meta)
|
|
4793
|
+
-> true
|
|
4794
|
+
; nonvar(Goal),
|
|
4795
|
+
callable(Goal),
|
|
4796
|
+
functor(Goal, Name, _),
|
|
4797
|
+
repl_reserved_name(Name)
|
|
4798
|
+
-> throw(error(permission_error(execute, reserved_predicate, Name),
|
|
4799
|
+
context(mi_call/3, 'cannot invoke a reserved REPL predicate')))
|
|
4800
|
+
; true
|
|
4801
|
+
).
|
|
4802
|
+
|
|
4803
|
+
guard_reserved_direct(Goal, StateIn) :-
|
|
4804
|
+
( is_dict(StateIn), get_dict(frame_kind, StateIn, meta)
|
|
4805
|
+
-> fail
|
|
4806
|
+
; nonvar(Goal),
|
|
4807
|
+
callable(Goal),
|
|
4808
|
+
functor(Goal, Name, _),
|
|
4809
|
+
repl_reserved_name(Name)
|
|
4810
|
+
).
|
|
4811
|
+
|
|
4812
|
+
%% Untransformed task/N and prompt/N (N >= 2) are source-level sugar rewritten
|
|
4813
|
+
%% to task_named/3 / prompt_named/3 by transform_task_calls/3 at load time.
|
|
4814
|
+
%% There is no runtime handler for the untransformed form, so it would match no
|
|
4815
|
+
%% clause and fail silently (looking like an inexplicable "0 solutions"). Fail
|
|
4816
|
+
%% loudly instead: reaching here means a clause was asserted/run without the
|
|
4817
|
+
%% load-time preprocessing (assert/assert_many now apply it, so this is a guard).
|
|
4818
|
+
mi_call(Goal, _StateIn, _StateOut) :-
|
|
4819
|
+
compound(Goal),
|
|
4820
|
+
functor(Goal, Name, Arity),
|
|
4821
|
+
Arity >= 2,
|
|
4822
|
+
( Name == task ; Name == prompt ),
|
|
4823
|
+
!,
|
|
4824
|
+
throw(error(domain_error(unrewritten_task_form, Goal),
|
|
4825
|
+
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'))).
|
|
4826
|
+
|
|
2475
4827
|
%% mi_call(Goal, +StateIn, -StateOut)
|
|
2476
4828
|
%% Catch-all for built-in and user-defined predicates
|
|
2477
4829
|
%% MUST NOT match special predicates that have dedicated handlers
|
|
@@ -2689,6 +5041,17 @@ expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
|
2689
5041
|
rebuild_goal(Functor, StringArg, ExpandedRestArgs, ExpandedGoal)
|
|
2690
5042
|
).
|
|
2691
5043
|
|
|
5044
|
+
% Goals whose interpolated string is NOT the first argument (verify/3, judge/2,
|
|
5045
|
+
% choose/4, rate/4, probability/3, holds/2,3, and any user predicate): hoist the
|
|
5046
|
+
% interpolation into a preceding goal.
|
|
5047
|
+
expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
5048
|
+
compound(Goal),
|
|
5049
|
+
Goal =.. [Functor|Args],
|
|
5050
|
+
expand_args_hoist(Args, Bindings, PreGoals, ExpandedArgs, true),
|
|
5051
|
+
!,
|
|
5052
|
+
Bare =.. [Functor|ExpandedArgs],
|
|
5053
|
+
prepend_goals(PreGoals, Bare, ExpandedGoal).
|
|
5054
|
+
|
|
2692
5055
|
% Other goals - expand arguments
|
|
2693
5056
|
expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
2694
5057
|
compound(Goal), !,
|
|
@@ -2753,6 +5116,34 @@ expand_rest_args([A|As], Bindings, [EA|EAs]) :-
|
|
|
2753
5116
|
expand_body_interp(A, Bindings, EA),
|
|
2754
5117
|
expand_rest_args(As, Bindings, EAs).
|
|
2755
5118
|
|
|
5119
|
+
%% expand_args_hoist(+Args, +Bindings, -PreGoals, -Args2, -Hoisted)
|
|
5120
|
+
%% Expand each argument; any interpolated string becomes a goal in PreGoals and
|
|
5121
|
+
%% its argument becomes the temp variable that goal binds.
|
|
5122
|
+
expand_args_hoist([], _, [], [], false).
|
|
5123
|
+
expand_args_hoist([Arg|Args], Bindings, PreGoals, [Expanded|EArgs], Hoisted) :-
|
|
5124
|
+
expand_arg_hoist(Arg, Bindings, Pre, Expanded, ThisHoisted),
|
|
5125
|
+
expand_args_hoist(Args, Bindings, Rest, EArgs, RestHoisted),
|
|
5126
|
+
append(Pre, Rest, PreGoals),
|
|
5127
|
+
( ThisHoisted == true
|
|
5128
|
+
-> Hoisted = true
|
|
5129
|
+
; Hoisted = RestHoisted
|
|
5130
|
+
).
|
|
5131
|
+
|
|
5132
|
+
expand_arg_hoist(Arg, Bindings, [FormatGoal], TempVar, true) :-
|
|
5133
|
+
( string(Arg)
|
|
5134
|
+
; atom(Arg)
|
|
5135
|
+
),
|
|
5136
|
+
string_needs_interpolation(Arg),
|
|
5137
|
+
!,
|
|
5138
|
+
build_format_call(Arg, Bindings, TempVar, FormatGoal).
|
|
5139
|
+
expand_arg_hoist(Arg, _, [], Arg, false).
|
|
5140
|
+
|
|
5141
|
+
%% prepend_goals(+PreGoals, +Goal, -Expanded)
|
|
5142
|
+
prepend_goals([], Goal, Goal) :- !.
|
|
5143
|
+
prepend_goals(Goals, Goal, Expanded) :-
|
|
5144
|
+
list_to_conjunction(Goals, Conj),
|
|
5145
|
+
Expanded = (Conj, Goal).
|
|
5146
|
+
|
|
2756
5147
|
%% flatten_conjunction(+A, +B, -Conj)
|
|
2757
5148
|
%% Flatten nested conjunctions properly
|
|
2758
5149
|
flatten_conjunction((A1, A2), B, Result) :- !,
|