deepclause-sdk 0.0.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 +446 -0
- package/dist/agent.d.ts +44 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +518 -0
- package/dist/agent.js.map +1 -0
- package/dist/cli/commands.d.ts +37 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +105 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/compile.d.ts +88 -0
- package/dist/cli/compile.d.ts.map +1 -0
- package/dist/cli/compile.js +362 -0
- package/dist/cli/compile.js.map +1 -0
- package/dist/cli/config.d.ts +265 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +272 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +287 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/mcp.d.ts +56 -0
- package/dist/cli/mcp.d.ts.map +1 -0
- package/dist/cli/mcp.js +138 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/prompt.d.ts +20 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +669 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/run.d.ts +33 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +429 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/search.d.ts +25 -0
- package/dist/cli/search.d.ts.map +1 -0
- package/dist/cli/search.js +125 -0
- package/dist/cli/search.js.map +1 -0
- package/dist/cli/tools.d.ts +36 -0
- package/dist/cli/tools.d.ts.map +1 -0
- package/dist/cli/tools.js +204 -0
- package/dist/cli/tools.js.map +1 -0
- package/dist/cli/tui/index.d.ts +22 -0
- package/dist/cli/tui/index.d.ts.map +1 -0
- package/dist/cli/tui/index.js +29 -0
- package/dist/cli/tui/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/prolog/bridge.d.ts +21 -0
- package/dist/prolog/bridge.d.ts.map +1 -0
- package/dist/prolog/bridge.js +226 -0
- package/dist/prolog/bridge.js.map +1 -0
- package/dist/prolog/loader.d.ts +40 -0
- package/dist/prolog/loader.d.ts.map +1 -0
- package/dist/prolog/loader.js +133 -0
- package/dist/prolog/loader.js.map +1 -0
- package/dist/prolog-src/deepclause_memory.pl +45 -0
- package/dist/prolog-src/deepclause_mi.pl +1978 -0
- package/dist/prolog-src/deepclause_mi.pl.bak +570 -0
- package/dist/prolog-src/deepclause_strings.pl +89 -0
- package/dist/runner.d.ts +143 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +1095 -0
- package/dist/runner.js.map +1 -0
- package/dist/sdk.d.ts +9 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +131 -0
- package/dist/sdk.js.map +1 -0
- package/dist/tools.d.ts +22 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +138 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +186 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +79 -0
- package/src/prolog-src/deepclause_memory.pl +45 -0
- package/src/prolog-src/deepclause_mi.pl +1978 -0
- package/src/prolog-src/deepclause_mi.pl.bak +570 -0
- package/src/prolog-src/deepclause_strings.pl +89 -0
- package/vendor/swipl-wasm/LICENSE.txt +41 -0
- package/vendor/swipl-wasm/dist/bin/index.js +25 -0
- package/vendor/swipl-wasm/dist/common.d.ts +88 -0
- package/vendor/swipl-wasm/dist/generateImage.d.ts +6 -0
- package/vendor/swipl-wasm/dist/generateImage.js +76 -0
- package/vendor/swipl-wasm/dist/index.d.ts +2 -0
- package/vendor/swipl-wasm/dist/index.js +1 -0
- package/vendor/swipl-wasm/dist/loadImage.d.ts +2 -0
- package/vendor/swipl-wasm/dist/loadImage.js +10 -0
- package/vendor/swipl-wasm/dist/loadImageDefault.d.ts +2 -0
- package/vendor/swipl-wasm/dist/loadImageDefault.js +11 -0
- package/vendor/swipl-wasm/dist/strToBuffer.d.ts +8 -0
- package/vendor/swipl-wasm/dist/strToBuffer.js +41 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-bundle-no-data.d.ts +2 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-bundle-no-data.js +2 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-bundle.d.ts +2 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-bundle.js +2 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-web.d.ts +2 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-web.data +0 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-web.js +2 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-web.wasm +0 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-win.js +1 -0
- package/vendor/swipl-wasm/dist/swipl/swipl-win.wasm +0 -0
- package/vendor/swipl-wasm/dist/swipl/swipl.d.ts +2 -0
- package/vendor/swipl-wasm/dist/swipl/swipl.js +1 -0
- package/vendor/swipl-wasm/dist/swipl/swipl.wasm +0 -0
- package/vendor/swipl-wasm/dist/swipl-node.d.ts +2 -0
- package/vendor/swipl-wasm/dist/swipl-node.js +17 -0
- package/vendor/swipl-wasm/package.json +129 -0
|
@@ -0,0 +1,1978 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deepclause_mi.pl - Meta-interpreter for DeepClause SDK
|
|
3
|
+
*
|
|
4
|
+
* The simplified meta-interpreter that handles:
|
|
5
|
+
* - task/1 and task/N predicates (agent loops)
|
|
6
|
+
* - exec/2 predicate (external tool calls)
|
|
7
|
+
* - Memory predicates (system, user) - now backtrackable via state
|
|
8
|
+
* - Output predicates (answer, yield, log)
|
|
9
|
+
* - Parameter handling
|
|
10
|
+
*
|
|
11
|
+
* Key design decisions:
|
|
12
|
+
* - No @-predicates - all LLM interaction via task()
|
|
13
|
+
* - Cooperative execution via engine yields
|
|
14
|
+
* - BACKTRACKABLE MEMORY via state threading (no external dynamic predicates)
|
|
15
|
+
* - State = state{memory: [...], params: {...}}
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
:- module(deepclause_mi, [
|
|
19
|
+
parse_dml/5,
|
|
20
|
+
create_engine/5,
|
|
21
|
+
step_engine/4,
|
|
22
|
+
destroy_engine/1,
|
|
23
|
+
post_agent_result/4,
|
|
24
|
+
post_exec_result/3,
|
|
25
|
+
provide_input/2,
|
|
26
|
+
mi/3,
|
|
27
|
+
% Tool engine predicates for isolated tool execution
|
|
28
|
+
create_tool_engine/4,
|
|
29
|
+
step_tool_engine/4,
|
|
30
|
+
destroy_tool_engine/1,
|
|
31
|
+
post_tool_agent_result/5
|
|
32
|
+
]).
|
|
33
|
+
|
|
34
|
+
:- use_module(deepclause_strings).
|
|
35
|
+
|
|
36
|
+
%% Allow mi_call/3 clauses to be non-contiguous
|
|
37
|
+
:- discontiguous mi_call/3.
|
|
38
|
+
:- discontiguous transform_task_calls/3.
|
|
39
|
+
|
|
40
|
+
%% Dynamic predicates for session state (engine management only, not memory!)
|
|
41
|
+
:- dynamic session_engine/2. % session_engine(SessionId, Engine)
|
|
42
|
+
:- dynamic session_params/2. % session_params(SessionId, ParamsDict)
|
|
43
|
+
:- dynamic session_user_tools/2. % session_user_tools(SessionId, ToolName)
|
|
44
|
+
:- dynamic session_user_tool_schema/3. % session_user_tool_schema(SessionId, ToolName, Schema)
|
|
45
|
+
:- dynamic session_pending_input/2. % session_pending_input(SessionId, Input)
|
|
46
|
+
:- dynamic session_agent_result/2. % session_agent_result(SessionId, Result)
|
|
47
|
+
:- dynamic session_exec_result/2. % session_exec_result(SessionId, Result)
|
|
48
|
+
:- dynamic session_trace_log/2. % session_trace_log(SessionId, TraceLog) - accumulated trace entries
|
|
49
|
+
:- dynamic session_trace_enabled/2. % session_trace_enabled(SessionId, true/false)
|
|
50
|
+
:- dynamic session_pending_signal/2. % session_pending_signal(SessionId, Signal) - fallback for engine_fetch
|
|
51
|
+
:- dynamic session_user_input/2. % session_user_input(SessionId, Input) - for ask_user tool
|
|
52
|
+
|
|
53
|
+
%% ============================================================
|
|
54
|
+
%% DML Parsing
|
|
55
|
+
%% ============================================================
|
|
56
|
+
|
|
57
|
+
%% parse_dml(+FilePath, +SessionId, +MemoryId, +Params, -Error)
|
|
58
|
+
%% Parse a DML file and load its clauses into the session module
|
|
59
|
+
%% Also asserts param/2 facts from the Params dict
|
|
60
|
+
parse_dml(FilePath, SessionId, _MemoryId, Params, Error) :-
|
|
61
|
+
catch(
|
|
62
|
+
(
|
|
63
|
+
read_file_to_string(FilePath, Code, []),
|
|
64
|
+
assert_params(SessionId, Params),
|
|
65
|
+
parse_dml_string(Code, SessionId),
|
|
66
|
+
Error = none
|
|
67
|
+
),
|
|
68
|
+
ParseError,
|
|
69
|
+
format(atom(Error), '~w', [ParseError])
|
|
70
|
+
).
|
|
71
|
+
|
|
72
|
+
%% assert_params(+SessionId, +Params)
|
|
73
|
+
%% Assert param/2 facts from the Params dict into the session module
|
|
74
|
+
assert_params(SessionId, Params) :-
|
|
75
|
+
( is_dict(Params)
|
|
76
|
+
-> dict_pairs(Params, _, Pairs),
|
|
77
|
+
forall(
|
|
78
|
+
member(Key-Value, Pairs),
|
|
79
|
+
assertz(SessionId:param(Key, Value))
|
|
80
|
+
)
|
|
81
|
+
; true
|
|
82
|
+
).
|
|
83
|
+
|
|
84
|
+
%% parse_dml_string(+Code, +SessionId)
|
|
85
|
+
%% Parse DML code from a string
|
|
86
|
+
parse_dml_string(Code, SessionId) :-
|
|
87
|
+
open_string(Code, Stream),
|
|
88
|
+
parse_clauses(Stream, SessionId),
|
|
89
|
+
close(Stream).
|
|
90
|
+
|
|
91
|
+
%% parse_clauses(+Stream, +SessionId)
|
|
92
|
+
%% Read and process all clauses from a stream
|
|
93
|
+
%% Uses variable_names to enable compile-time string interpolation
|
|
94
|
+
parse_clauses(Stream, SessionId) :-
|
|
95
|
+
read_term(Stream, Term, [module(SessionId), variable_names(Bindings)]),
|
|
96
|
+
( Term == end_of_file
|
|
97
|
+
-> true
|
|
98
|
+
; % First, transform task/N calls to include variable names
|
|
99
|
+
transform_task_calls(Term, Bindings, TransformedTerm),
|
|
100
|
+
% Then expand string interpolations
|
|
101
|
+
expand_interpolations(TransformedTerm, Bindings, ExpandedTerm),
|
|
102
|
+
process_clause(ExpandedTerm, SessionId),
|
|
103
|
+
parse_clauses(Stream, SessionId)
|
|
104
|
+
).
|
|
105
|
+
|
|
106
|
+
%% ============================================================
|
|
107
|
+
%% Task Call Transformation (compile-time)
|
|
108
|
+
%% ============================================================
|
|
109
|
+
|
|
110
|
+
%% transform_task_calls(+Term, +Bindings, -TransformedTerm)
|
|
111
|
+
%% Transform task(Desc, Var1, ...) to task_named(Desc, [Var1, ...], ['Name1', ...])
|
|
112
|
+
transform_task_calls(Var, _, Var) :- var(Var), !.
|
|
113
|
+
transform_task_calls((Head :- Body), Bindings, (Head :- TransformedBody)) :- !,
|
|
114
|
+
transform_task_calls(Body, Bindings, TransformedBody).
|
|
115
|
+
transform_task_calls((A, B), Bindings, (TA, TB)) :- !,
|
|
116
|
+
transform_task_calls(A, Bindings, TA),
|
|
117
|
+
transform_task_calls(B, Bindings, TB).
|
|
118
|
+
transform_task_calls((A ; B), Bindings, (TA ; TB)) :- !,
|
|
119
|
+
transform_task_calls(A, Bindings, TA),
|
|
120
|
+
transform_task_calls(B, Bindings, TB).
|
|
121
|
+
transform_task_calls((A -> B), Bindings, (TA -> TB)) :- !,
|
|
122
|
+
transform_task_calls(A, Bindings, TA),
|
|
123
|
+
transform_task_calls(B, Bindings, TB).
|
|
124
|
+
transform_task_calls(\+ A, Bindings, \+ TA) :- !,
|
|
125
|
+
transform_task_calls(A, Bindings, TA).
|
|
126
|
+
|
|
127
|
+
%% Transform task/2 to task_named/3
|
|
128
|
+
transform_task_calls(task(Desc, V1), Bindings, task_named(Desc, [V1], Names)) :- !,
|
|
129
|
+
maplist(var_to_name(Bindings), [V1], Names).
|
|
130
|
+
|
|
131
|
+
%% Transform task/3 to task_named/3
|
|
132
|
+
transform_task_calls(task(Desc, V1, V2), Bindings, task_named(Desc, [V1, V2], Names)) :- !,
|
|
133
|
+
maplist(var_to_name(Bindings), [V1, V2], Names).
|
|
134
|
+
|
|
135
|
+
%% Transform task/4 to task_named/3
|
|
136
|
+
transform_task_calls(task(Desc, V1, V2, V3), Bindings, task_named(Desc, [V1, V2, V3], Names)) :- !,
|
|
137
|
+
maplist(var_to_name(Bindings), [V1, V2, V3], Names).
|
|
138
|
+
|
|
139
|
+
%% Transform task/5 to task_named/3
|
|
140
|
+
transform_task_calls(task(Desc, V1, V2, V3, V4), Bindings, task_named(Desc, [V1, V2, V3, V4], Names)) :- !,
|
|
141
|
+
maplist(var_to_name(Bindings), [V1, V2, V3, V4], Names).
|
|
142
|
+
|
|
143
|
+
%% Transform prompt/2 to prompt_named/3
|
|
144
|
+
transform_task_calls(prompt(Desc, V1), Bindings, prompt_named(Desc, [V1], Names)) :- !,
|
|
145
|
+
maplist(var_to_name(Bindings), [V1], Names).
|
|
146
|
+
|
|
147
|
+
%% Transform prompt/3 to prompt_named/3
|
|
148
|
+
transform_task_calls(prompt(Desc, V1, V2), Bindings, prompt_named(Desc, [V1, V2], Names)) :- !,
|
|
149
|
+
maplist(var_to_name(Bindings), [V1, V2], Names).
|
|
150
|
+
|
|
151
|
+
%% Transform prompt/4 to prompt_named/3
|
|
152
|
+
transform_task_calls(prompt(Desc, V1, V2, V3), Bindings, prompt_named(Desc, [V1, V2, V3], Names)) :- !,
|
|
153
|
+
maplist(var_to_name(Bindings), [V1, V2, V3], Names).
|
|
154
|
+
|
|
155
|
+
%% Recurse into arbitrary compound terms (like with_tools/2, without_tools/2, etc.)
|
|
156
|
+
transform_task_calls(Term, Bindings, TransformedTerm) :-
|
|
157
|
+
compound(Term),
|
|
158
|
+
\+ is_list(Term), % Don't decompose lists
|
|
159
|
+
Term =.. [Functor|Args],
|
|
160
|
+
Args \= [], % Has at least one argument
|
|
161
|
+
!,
|
|
162
|
+
maplist(transform_task_calls_arg(Bindings), Args, TransformedArgs),
|
|
163
|
+
TransformedTerm =.. [Functor|TransformedArgs].
|
|
164
|
+
|
|
165
|
+
%% Helper to transform each argument
|
|
166
|
+
transform_task_calls_arg(Bindings, Arg, TransformedArg) :-
|
|
167
|
+
transform_task_calls(Arg, Bindings, TransformedArg).
|
|
168
|
+
|
|
169
|
+
%% Default: atoms and numbers pass through unchanged
|
|
170
|
+
transform_task_calls(Term, _, Term).
|
|
171
|
+
|
|
172
|
+
%% var_to_name(+Bindings, +Var, -Name)
|
|
173
|
+
%% Look up a variable's name from the bindings list
|
|
174
|
+
var_to_name(Bindings, Var, Name) :-
|
|
175
|
+
( member(N=V, Bindings), V == Var
|
|
176
|
+
-> Name = N
|
|
177
|
+
; Name = 'Var' % Fallback if not found
|
|
178
|
+
).
|
|
179
|
+
|
|
180
|
+
%% process_clause(+Term, +SessionId)
|
|
181
|
+
%% Process a single clause and add to session
|
|
182
|
+
process_clause((:- Directive), SessionId) :-
|
|
183
|
+
!,
|
|
184
|
+
process_directive(Directive, SessionId).
|
|
185
|
+
|
|
186
|
+
%% Handle tool/2 with description: tool(Head, Description) :- Body
|
|
187
|
+
process_clause((tool(ToolHead, Description) :- Body), SessionId) :-
|
|
188
|
+
!,
|
|
189
|
+
extract_tool_schema(ToolHead, Description, ToolName, Schema),
|
|
190
|
+
assertz(session_user_tools(SessionId, ToolName)),
|
|
191
|
+
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
192
|
+
% Store the source code for the tool description
|
|
193
|
+
format(string(SourceCode), "tool(~w, ~q) :-~n ~w.", [ToolHead, Description, Body]),
|
|
194
|
+
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
195
|
+
% Assert the tool implementation (use just ToolHead for execution)
|
|
196
|
+
assertz(SessionId:(tool(ToolHead) :- Body)).
|
|
197
|
+
|
|
198
|
+
%% Handle tool/1 without description: tool(Head) :- Body
|
|
199
|
+
process_clause((tool(ToolHead) :- Body), SessionId) :-
|
|
200
|
+
!,
|
|
201
|
+
extract_tool_schema(ToolHead, none, ToolName, Schema),
|
|
202
|
+
assertz(session_user_tools(SessionId, ToolName)),
|
|
203
|
+
assertz(session_user_tool_schema(SessionId, ToolName, Schema)),
|
|
204
|
+
% Store the source code for the tool description
|
|
205
|
+
format(string(SourceCode), "tool(~w) :-~n ~w.", [ToolHead, Body]),
|
|
206
|
+
assertz(SessionId:tool_source(ToolName, SourceCode)),
|
|
207
|
+
% Assert the tool implementation
|
|
208
|
+
assertz(SessionId:(tool(ToolHead) :- Body)).
|
|
209
|
+
|
|
210
|
+
process_clause((Head :- Body), SessionId) :-
|
|
211
|
+
!,
|
|
212
|
+
% Regular clause - assert it
|
|
213
|
+
assertz(SessionId:(Head :- Body)).
|
|
214
|
+
|
|
215
|
+
process_clause(Fact, SessionId) :-
|
|
216
|
+
% Simple fact
|
|
217
|
+
assertz(SessionId:Fact).
|
|
218
|
+
|
|
219
|
+
%% ============================================================
|
|
220
|
+
%% Tool Schema Extraction
|
|
221
|
+
%% ============================================================
|
|
222
|
+
|
|
223
|
+
%% extract_tool_schema(+ToolHead, +Description, -ToolName, -Schema)
|
|
224
|
+
extract_tool_schema(ToolHead, Description, ToolName, Schema) :-
|
|
225
|
+
ToolHead =.. [ToolName|Args],
|
|
226
|
+
length(Args, Arity),
|
|
227
|
+
extract_params(Args, 1, Arity, Inputs, Outputs),
|
|
228
|
+
(Description == none -> Desc = "" ; Desc = Description),
|
|
229
|
+
Schema = schema{
|
|
230
|
+
name: ToolName,
|
|
231
|
+
description: Desc,
|
|
232
|
+
inputs: Inputs,
|
|
233
|
+
outputs: Outputs
|
|
234
|
+
}.
|
|
235
|
+
|
|
236
|
+
%% extract_params(+Args, +Index, +Arity, -Inputs, -Outputs)
|
|
237
|
+
extract_params([], _, _, [], []) :- !.
|
|
238
|
+
extract_params([_Arg|Rest], Index, Arity, Inputs, Outputs) :-
|
|
239
|
+
format(atom(Name), 'arg~w', [Index]),
|
|
240
|
+
Type = string,
|
|
241
|
+
NextIndex is Index + 1,
|
|
242
|
+
extract_params(Rest, NextIndex, Arity, RestInputs, RestOutputs),
|
|
243
|
+
Param = param{name: Name, type: Type},
|
|
244
|
+
( Index == Arity
|
|
245
|
+
-> Inputs = RestInputs, Outputs = [Param|RestOutputs]
|
|
246
|
+
; Inputs = [Param|RestInputs], Outputs = RestOutputs
|
|
247
|
+
).
|
|
248
|
+
|
|
249
|
+
%% process_directive(+Directive, +SessionId)
|
|
250
|
+
process_directive(param(Key, Desc), SessionId) :-
|
|
251
|
+
!,
|
|
252
|
+
assertz(SessionId:param_decl(Key, Desc)).
|
|
253
|
+
process_directive(param(Key, Desc, Default), SessionId) :-
|
|
254
|
+
!,
|
|
255
|
+
assertz(SessionId:param_decl(Key, Desc, Default)).
|
|
256
|
+
process_directive(_, _).
|
|
257
|
+
|
|
258
|
+
%% ============================================================
|
|
259
|
+
%% Engine Management
|
|
260
|
+
%% ============================================================
|
|
261
|
+
|
|
262
|
+
%% create_engine(+SessionId, +MemoryId, +Args, +Params, -Engine)
|
|
263
|
+
%% Args is a list of positional arguments for agent_main
|
|
264
|
+
%% Params is a dict of named parameters (already asserted as param/2 facts)
|
|
265
|
+
create_engine(SessionId, _MemoryId, Args, Params, Engine) :-
|
|
266
|
+
assertz(session_params(SessionId, Params)),
|
|
267
|
+
determine_agent_goal(SessionId, Args, Goal),
|
|
268
|
+
% Check if tracing is enabled and store in session state
|
|
269
|
+
(get_dict(trace, Params, true) -> TraceEnabled = true ; TraceEnabled = false),
|
|
270
|
+
assertz(session_trace_enabled(SessionId, TraceEnabled)),
|
|
271
|
+
assertz(session_trace_log(SessionId, [])),
|
|
272
|
+
% Create initial state with empty memory, params, context stack, and trace depth
|
|
273
|
+
InitialState = state{memory: [], params: Params, context_stack: [], depth: 0},
|
|
274
|
+
% Create the engine - pass SessionId and initial state to mi/3
|
|
275
|
+
engine_create(_,
|
|
276
|
+
deepclause_mi:mi(Goal, InitialState, SessionId),
|
|
277
|
+
Engine),
|
|
278
|
+
assertz(session_engine(SessionId, Engine)).
|
|
279
|
+
|
|
280
|
+
%% determine_agent_goal(+SessionId, +Args, -Goal)
|
|
281
|
+
%% Args is a list of positional arguments - passed directly to agent_main
|
|
282
|
+
%% Prefers arity that matches the number of arguments provided
|
|
283
|
+
determine_agent_goal(SessionId, [], SessionId:agent_main) :-
|
|
284
|
+
current_predicate(SessionId:agent_main/0), !.
|
|
285
|
+
determine_agent_goal(SessionId, [Arg1], SessionId:agent_main(Arg1)) :-
|
|
286
|
+
current_predicate(SessionId:agent_main/1), !.
|
|
287
|
+
determine_agent_goal(SessionId, [Arg1, Arg2], SessionId:agent_main(Arg1, Arg2)) :-
|
|
288
|
+
current_predicate(SessionId:agent_main/2), !.
|
|
289
|
+
determine_agent_goal(SessionId, [Arg1, Arg2, Arg3], SessionId:agent_main(Arg1, Arg2, Arg3)) :-
|
|
290
|
+
current_predicate(SessionId:agent_main/3), !.
|
|
291
|
+
% Fallback: if no exact arity match, try best fit
|
|
292
|
+
determine_agent_goal(SessionId, Args, Goal) :-
|
|
293
|
+
current_predicate(SessionId:agent_main/1), !,
|
|
294
|
+
( Args = [Arg1|_]
|
|
295
|
+
-> Goal = SessionId:agent_main(Arg1)
|
|
296
|
+
; Goal = SessionId:agent_main(_)
|
|
297
|
+
).
|
|
298
|
+
determine_agent_goal(SessionId, Args, Goal) :-
|
|
299
|
+
current_predicate(SessionId:agent_main/2), !,
|
|
300
|
+
( Args = [Arg1, Arg2|_]
|
|
301
|
+
-> Goal = SessionId:agent_main(Arg1, Arg2)
|
|
302
|
+
; Args = [Arg1|_]
|
|
303
|
+
-> Goal = SessionId:agent_main(Arg1, _)
|
|
304
|
+
; Goal = SessionId:agent_main(_, _)
|
|
305
|
+
).
|
|
306
|
+
determine_agent_goal(SessionId, Args, Goal) :-
|
|
307
|
+
current_predicate(SessionId:agent_main/3), !,
|
|
308
|
+
( Args = [Arg1, Arg2, Arg3|_]
|
|
309
|
+
-> Goal = SessionId:agent_main(Arg1, Arg2, Arg3)
|
|
310
|
+
; Args = [Arg1, Arg2|_]
|
|
311
|
+
-> Goal = SessionId:agent_main(Arg1, Arg2, _)
|
|
312
|
+
; Args = [Arg1|_]
|
|
313
|
+
-> Goal = SessionId:agent_main(Arg1, _, _)
|
|
314
|
+
; Goal = SessionId:agent_main(_, _, _)
|
|
315
|
+
).
|
|
316
|
+
determine_agent_goal(SessionId, _Args, SessionId:agent_main).
|
|
317
|
+
|
|
318
|
+
%% step_engine(+SessionId, -Status, -Content, -Payload)
|
|
319
|
+
step_engine(SessionId, Status, Content, Payload) :-
|
|
320
|
+
( session_engine(SessionId, Engine)
|
|
321
|
+
-> catch(
|
|
322
|
+
( engine_next(Engine, Result)
|
|
323
|
+
-> process_engine_result(Result, Status, Content, Payload)
|
|
324
|
+
; % Engine finished - return trace if enabled
|
|
325
|
+
Status = finished,
|
|
326
|
+
Content = '',
|
|
327
|
+
( session_trace_enabled(SessionId, true),
|
|
328
|
+
session_trace_log(SessionId, TraceLog)
|
|
329
|
+
-> Payload = payload{trace: TraceLog}
|
|
330
|
+
; Payload = none
|
|
331
|
+
)
|
|
332
|
+
),
|
|
333
|
+
Error,
|
|
334
|
+
( format(atom(ErrMsg), 'Engine error: ~w', [Error]),
|
|
335
|
+
Status = error,
|
|
336
|
+
Content = ErrMsg,
|
|
337
|
+
Payload = none
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
; Status = error,
|
|
341
|
+
Content = 'No engine found for session',
|
|
342
|
+
Payload = none
|
|
343
|
+
).
|
|
344
|
+
|
|
345
|
+
%% process_engine_result(+Result, -Status, -Content, -Payload)
|
|
346
|
+
process_engine_result(output(Text), output, Text, none) :- !.
|
|
347
|
+
process_engine_result(log(Text), log, Text, none) :- !.
|
|
348
|
+
process_engine_result(answer(Text), answer, Text, none) :- !.
|
|
349
|
+
%% Note: Memory and tool scope are now passed in the payload for the agent loop
|
|
350
|
+
process_engine_result(request_agent_loop(Desc, Vars, Tools, Memory, ToolScope), request_agent_loop, '',
|
|
351
|
+
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: ToolScope}) :- !.
|
|
352
|
+
%% Legacy 4-arg format (no tool scope)
|
|
353
|
+
process_engine_result(request_agent_loop(Desc, Vars, Tools, Memory), request_agent_loop, '',
|
|
354
|
+
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: none}) :- !.
|
|
355
|
+
process_engine_result(request_exec(Tool, Args), request_exec, '',
|
|
356
|
+
payload{toolName: Tool, args: Args}) :- !.
|
|
357
|
+
%% Tool result from inline tool execution
|
|
358
|
+
process_engine_result(tool_result(Result), tool_result, '',
|
|
359
|
+
payload{result: Result}) :- !.
|
|
360
|
+
process_engine_result(wait_input(Prompt), wait_input, Prompt, none) :- !.
|
|
361
|
+
process_engine_result(error(Msg), error, Msg, none) :- !.
|
|
362
|
+
process_engine_result(Other, error, Msg, none) :-
|
|
363
|
+
format(atom(Msg), 'Unknown engine result: ~w', [Other]).
|
|
364
|
+
|
|
365
|
+
%% destroy_engine(+SessionId)
|
|
366
|
+
destroy_engine(SessionId) :-
|
|
367
|
+
( session_engine(SessionId, Engine)
|
|
368
|
+
-> catch(engine_destroy(Engine), _, true)
|
|
369
|
+
; true
|
|
370
|
+
),
|
|
371
|
+
retractall(session_engine(SessionId, _)),
|
|
372
|
+
retractall(session_params(SessionId, _)),
|
|
373
|
+
retractall(session_user_tools(SessionId, _)),
|
|
374
|
+
retractall(session_user_tool_schema(SessionId, _, _)),
|
|
375
|
+
retractall(session_pending_input(SessionId, _)),
|
|
376
|
+
retractall(session_agent_result(SessionId, _)),
|
|
377
|
+
retractall(session_exec_result(SessionId, _)),
|
|
378
|
+
retractall(session_trace_log(SessionId, _)),
|
|
379
|
+
retractall(session_trace_enabled(SessionId, _)),
|
|
380
|
+
( current_module(SessionId)
|
|
381
|
+
-> catch(
|
|
382
|
+
( findall(Head,
|
|
383
|
+
(current_predicate(SessionId:Name/Arity),
|
|
384
|
+
functor(Head, Name, Arity),
|
|
385
|
+
clause(SessionId:Head, _)),
|
|
386
|
+
Heads),
|
|
387
|
+
forall(member(H, Heads), retractall(SessionId:H))
|
|
388
|
+
),
|
|
389
|
+
_,
|
|
390
|
+
true
|
|
391
|
+
)
|
|
392
|
+
; true
|
|
393
|
+
).
|
|
394
|
+
|
|
395
|
+
%% ============================================================
|
|
396
|
+
%% Result Posting (from JavaScript)
|
|
397
|
+
%% ============================================================
|
|
398
|
+
|
|
399
|
+
%% post_agent_result(+SessionId, +Success, +Variables, +Messages)
|
|
400
|
+
%% Messages is a list of message{role: Role, content: Content} dicts
|
|
401
|
+
post_agent_result(SessionId, Success, Variables, Messages) :-
|
|
402
|
+
assertz(session_agent_result(SessionId, result{success: Success, variables: Variables, messages: Messages})),
|
|
403
|
+
post_signal_to_engine(SessionId, agent_done).
|
|
404
|
+
|
|
405
|
+
%% post_exec_result(+SessionId, +Status, +Result)
|
|
406
|
+
post_exec_result(SessionId, Status, Result) :-
|
|
407
|
+
retractall(session_exec_result(SessionId, _)),
|
|
408
|
+
assertz(session_exec_result(SessionId, result{status: Status, result: Result})),
|
|
409
|
+
post_signal_to_engine(SessionId, exec_done).
|
|
410
|
+
|
|
411
|
+
%% provide_input(+SessionId, +Input)
|
|
412
|
+
provide_input(SessionId, Input) :-
|
|
413
|
+
assertz(session_pending_input(SessionId, Input)),
|
|
414
|
+
post_signal_to_engine(SessionId, input_provided).
|
|
415
|
+
|
|
416
|
+
%% post_signal_to_engine(+SessionId, +Signal)
|
|
417
|
+
%% Posts a signal via dynamic predicate (most reliable for WASM engines)
|
|
418
|
+
%% Falls back to engine_post only if dynamic assert fails
|
|
419
|
+
post_signal_to_engine(SessionId, Signal) :-
|
|
420
|
+
% Always use dynamic predicate - more reliable than engine_post in WASM
|
|
421
|
+
assertz(session_pending_signal(SessionId, Signal)).
|
|
422
|
+
|
|
423
|
+
%% ============================================================
|
|
424
|
+
%% Meta-Interpreter Core - STATE THREADED VERSION
|
|
425
|
+
%% ============================================================
|
|
426
|
+
|
|
427
|
+
%% mi(+Goal, +StateIn, +SessionId)
|
|
428
|
+
%% Main meta-interpreter entry point
|
|
429
|
+
%% State = state{memory: [...], params: {...}}
|
|
430
|
+
mi(Goal, StateIn, SessionId) :-
|
|
431
|
+
nb_setval(current_session_id, SessionId),
|
|
432
|
+
catch(
|
|
433
|
+
mi_call(Goal, StateIn, _StateOut),
|
|
434
|
+
Error,
|
|
435
|
+
( Error == '$answer_commit'
|
|
436
|
+
-> true % answer/1 threw to commit - success, no backtracking
|
|
437
|
+
; format(atom(ErrMsg), 'Runtime error: ~w', [Error]),
|
|
438
|
+
engine_yield(error(ErrMsg)),
|
|
439
|
+
fail
|
|
440
|
+
)
|
|
441
|
+
),
|
|
442
|
+
!. % Cut to prevent backtracking after goal completes
|
|
443
|
+
|
|
444
|
+
%% ============================================================
|
|
445
|
+
%% State Helpers
|
|
446
|
+
%% ============================================================
|
|
447
|
+
|
|
448
|
+
%% add_memory(+StateIn, +Role, +Content, -StateOut)
|
|
449
|
+
add_memory(StateIn, Role, Content, StateOut) :-
|
|
450
|
+
Message = message{role: Role, content: Content},
|
|
451
|
+
OldMemory = StateIn.memory,
|
|
452
|
+
append(OldMemory, [Message], NewMemory),
|
|
453
|
+
StateOut = StateIn.put(memory, NewMemory).
|
|
454
|
+
|
|
455
|
+
%% get_memory(+State, -Memory)
|
|
456
|
+
get_memory(State, Memory) :-
|
|
457
|
+
Memory = State.memory.
|
|
458
|
+
|
|
459
|
+
%% get_params(+State, -Params)
|
|
460
|
+
get_params(State, Params) :-
|
|
461
|
+
Params = State.params.
|
|
462
|
+
|
|
463
|
+
%% get_context_stack(+State, -Stack)
|
|
464
|
+
get_context_stack(State, Stack) :-
|
|
465
|
+
( get_dict(context_stack, State, Stack)
|
|
466
|
+
-> true
|
|
467
|
+
; Stack = []
|
|
468
|
+
).
|
|
469
|
+
|
|
470
|
+
%% set_context_stack(+StateIn, +Stack, -StateOut)
|
|
471
|
+
set_context_stack(StateIn, Stack, StateOut) :-
|
|
472
|
+
StateOut = StateIn.put(context_stack, Stack).
|
|
473
|
+
|
|
474
|
+
%% set_memory(+StateIn, +Memory, -StateOut)
|
|
475
|
+
set_memory(StateIn, Memory, StateOut) :-
|
|
476
|
+
StateOut = StateIn.put(memory, Memory).
|
|
477
|
+
|
|
478
|
+
%% ============================================================
|
|
479
|
+
%% Tool Scope Helpers
|
|
480
|
+
%% ============================================================
|
|
481
|
+
%% Tool scoping allows manual control over which tools are available
|
|
482
|
+
%% in nested tasks. The scope is stored in state and passed through
|
|
483
|
+
%% the request_agent_loop payload.
|
|
484
|
+
|
|
485
|
+
%% get_tool_scope(+State, -Scope)
|
|
486
|
+
%% Returns the current tool scope, or 'none' if not set
|
|
487
|
+
get_tool_scope(State, Scope) :-
|
|
488
|
+
( get_dict(tool_scope, State, Scope)
|
|
489
|
+
-> true
|
|
490
|
+
; Scope = none
|
|
491
|
+
).
|
|
492
|
+
|
|
493
|
+
%% set_tool_scope(+StateIn, +Scope, -StateOut)
|
|
494
|
+
set_tool_scope(StateIn, Scope, StateOut) :-
|
|
495
|
+
StateOut = StateIn.put(tool_scope, Scope).
|
|
496
|
+
|
|
497
|
+
%% clear_tool_scope(+StateIn, -StateOut)
|
|
498
|
+
clear_tool_scope(StateIn, StateOut) :-
|
|
499
|
+
( get_dict(tool_scope, StateIn, _)
|
|
500
|
+
-> del_dict(tool_scope, StateIn, _, StateOut)
|
|
501
|
+
; StateOut = StateIn
|
|
502
|
+
).
|
|
503
|
+
|
|
504
|
+
%% ============================================================
|
|
505
|
+
%% Trace Helpers
|
|
506
|
+
%% ============================================================
|
|
507
|
+
|
|
508
|
+
%% get_depth(+State, -Depth)
|
|
509
|
+
get_depth(State, Depth) :-
|
|
510
|
+
( get_dict(depth, State, Depth)
|
|
511
|
+
-> true
|
|
512
|
+
; Depth = 0
|
|
513
|
+
).
|
|
514
|
+
|
|
515
|
+
%% set_depth(+StateIn, +Depth, -StateOut)
|
|
516
|
+
set_depth(StateIn, Depth, StateOut) :-
|
|
517
|
+
StateOut = StateIn.put(depth, Depth).
|
|
518
|
+
|
|
519
|
+
%% add_trace_entry(+SessionId, +Type, +Predicate, +Args, +Depth)
|
|
520
|
+
%% Add a trace entry if tracing is enabled
|
|
521
|
+
add_trace_entry(SessionId, Type, Predicate, Args, Depth) :-
|
|
522
|
+
( session_trace_enabled(SessionId, true)
|
|
523
|
+
-> get_time(Now),
|
|
524
|
+
Timestamp is round(Now * 1000), % Convert to milliseconds
|
|
525
|
+
Entry = trace{timestamp: Timestamp, type: Type, predicate: Predicate, args: Args, depth: Depth},
|
|
526
|
+
session_trace_log(SessionId, OldLog),
|
|
527
|
+
retract(session_trace_log(SessionId, OldLog)),
|
|
528
|
+
append(OldLog, [Entry], NewLog),
|
|
529
|
+
assertz(session_trace_log(SessionId, NewLog))
|
|
530
|
+
; true
|
|
531
|
+
).
|
|
532
|
+
|
|
533
|
+
%% add_trace_with_result(+SessionId, +Type, +Predicate, +Args, +Result, +Depth)
|
|
534
|
+
%% Add a trace entry with a result field
|
|
535
|
+
add_trace_with_result(SessionId, Type, Predicate, Args, Result, Depth) :-
|
|
536
|
+
( session_trace_enabled(SessionId, true)
|
|
537
|
+
-> get_time(Now),
|
|
538
|
+
Timestamp is round(Now * 1000),
|
|
539
|
+
Entry = trace{timestamp: Timestamp, type: Type, predicate: Predicate, args: Args, result: Result, depth: Depth},
|
|
540
|
+
session_trace_log(SessionId, OldLog),
|
|
541
|
+
retract(session_trace_log(SessionId, OldLog)),
|
|
542
|
+
append(OldLog, [Entry], NewLog),
|
|
543
|
+
assertz(session_trace_log(SessionId, NewLog))
|
|
544
|
+
; true
|
|
545
|
+
).
|
|
546
|
+
|
|
547
|
+
%% ============================================================
|
|
548
|
+
%% Agent Signal Loop - SIMPLIFIED
|
|
549
|
+
%% ============================================================
|
|
550
|
+
%%
|
|
551
|
+
%% With isolated tool execution, the agent signal loop is much simpler.
|
|
552
|
+
%% It only needs to wait for agent_done from the host after a task() completes.
|
|
553
|
+
%% Tool calls are handled separately via execute_tool_isolated/4.
|
|
554
|
+
|
|
555
|
+
%% handle_agent_signals(+SessionId, +StateIn, -StateOut)
|
|
556
|
+
%% Wait for agent_done signal from host after task() request.
|
|
557
|
+
%% State passes through unchanged (tool results don't modify task state).
|
|
558
|
+
handle_agent_signals(SessionId, StateIn, StateOut) :-
|
|
559
|
+
get_next_signal(SessionId, Signal),
|
|
560
|
+
handle_signal(SessionId, Signal, StateIn, StateOut).
|
|
561
|
+
|
|
562
|
+
%% get_next_signal(+SessionId, -Signal)
|
|
563
|
+
%% Retrieves the next signal, preferring dynamic predicate over engine_fetch.
|
|
564
|
+
get_next_signal(SessionId, Signal) :-
|
|
565
|
+
( retract(session_pending_signal(SessionId, Signal))
|
|
566
|
+
-> true
|
|
567
|
+
; catch(
|
|
568
|
+
engine_fetch(Signal),
|
|
569
|
+
_FetchError,
|
|
570
|
+
( retract(session_pending_signal(SessionId, Signal))
|
|
571
|
+
-> true
|
|
572
|
+
; Signal = error_signal(no_signal_available)
|
|
573
|
+
)
|
|
574
|
+
)
|
|
575
|
+
).
|
|
576
|
+
|
|
577
|
+
%% handle_signal(+SessionId, +Signal, +StateIn, -StateOut)
|
|
578
|
+
handle_signal(_SessionId, agent_done, State, State) :- !.
|
|
579
|
+
|
|
580
|
+
handle_signal(_SessionId, error_signal(Error), State, State) :-
|
|
581
|
+
!,
|
|
582
|
+
format(atom(ErrMsg), 'Engine fetch error: ~w', [Error]),
|
|
583
|
+
engine_yield(error(ErrMsg)).
|
|
584
|
+
|
|
585
|
+
handle_signal(SessionId, exec_done, StateIn, StateOut) :-
|
|
586
|
+
% Stale signal - ignore and continue waiting
|
|
587
|
+
!,
|
|
588
|
+
handle_agent_signals(SessionId, StateIn, StateOut).
|
|
589
|
+
|
|
590
|
+
handle_signal(SessionId, _Unknown, StateIn, StateOut) :-
|
|
591
|
+
% Unknown signal - ignore and continue waiting
|
|
592
|
+
!,
|
|
593
|
+
handle_agent_signals(SessionId, StateIn, StateOut).
|
|
594
|
+
|
|
595
|
+
%% call_tool_inline/6 is DEPRECATED - kept for backwards compatibility only
|
|
596
|
+
%% New code should use execute_tool_isolated/4 instead.
|
|
597
|
+
%% call_tool_inline(+SessionId, +ToolName, +Args, +StateIn, -StateOut, -Result)
|
|
598
|
+
%% Executes a DML-defined tool inline in the current engine.
|
|
599
|
+
%% The tool body runs through the meta-interpreter, sharing state.
|
|
600
|
+
%% Args is a list of input argument values from the tool call.
|
|
601
|
+
%% The tool may have additional output arguments which become unbound variables.
|
|
602
|
+
call_tool_inline(SessionId, ToolName, Args, StateIn, StateOut, Result) :-
|
|
603
|
+
% Convert tool name to atom
|
|
604
|
+
atom_string(ToolNameAtom, ToolName),
|
|
605
|
+
% Find the tool clause to determine its actual arity
|
|
606
|
+
% Tools are stored as: SessionId:(tool(Head) :- Body)
|
|
607
|
+
( clause(SessionId:tool(ToolPattern), Body),
|
|
608
|
+
ToolPattern =.. [ToolNameAtom|PatternArgs]
|
|
609
|
+
-> % Found the tool - create head args with inputs filled in
|
|
610
|
+
length(PatternArgs, TotalArity),
|
|
611
|
+
length(Args, InputArity),
|
|
612
|
+
% Create the head with input args + unbound output variables
|
|
613
|
+
length(HeadArgs, TotalArity),
|
|
614
|
+
% Fill in the input args
|
|
615
|
+
fill_args(HeadArgs, Args, InputArity),
|
|
616
|
+
ToolHead =.. [ToolNameAtom|HeadArgs],
|
|
617
|
+
% Unify with the pattern to bind variables in Body
|
|
618
|
+
ToolHead = ToolPattern,
|
|
619
|
+
% Execute the tool body through MI
|
|
620
|
+
% Wrap in catch to handle answer() commits - answer() yields and then throws
|
|
621
|
+
catch(
|
|
622
|
+
( mi_call(Body, StateIn, StateOut)
|
|
623
|
+
-> % Collect result from result/1 facts or output args
|
|
624
|
+
% Use catch to handle case where result/2 doesn't exist
|
|
625
|
+
( catch(SessionId:result(ToolNameAtom, R), _, fail)
|
|
626
|
+
-> Result = R,
|
|
627
|
+
catch(retract(SessionId:result(ToolNameAtom, R)), _, true)
|
|
628
|
+
; % No explicit result - return the last output arg if bound
|
|
629
|
+
( TotalArity > InputArity,
|
|
630
|
+
last(HeadArgs, LastArg),
|
|
631
|
+
nonvar(LastArg)
|
|
632
|
+
-> Result = LastArg
|
|
633
|
+
; Result = true
|
|
634
|
+
)
|
|
635
|
+
)
|
|
636
|
+
; % Tool body failed
|
|
637
|
+
StateOut = StateIn,
|
|
638
|
+
Result = false
|
|
639
|
+
),
|
|
640
|
+
'$answer_commit',
|
|
641
|
+
% answer() was called - it already yielded the answer text
|
|
642
|
+
% Just set success result and state
|
|
643
|
+
(StateOut = StateIn, Result = answered)
|
|
644
|
+
)
|
|
645
|
+
; % No DML body - should not happen if host checked
|
|
646
|
+
StateOut = StateIn,
|
|
647
|
+
format(atom(Result), 'Tool ~w not found', [ToolName])
|
|
648
|
+
).
|
|
649
|
+
|
|
650
|
+
%% ============================================================
|
|
651
|
+
%% ISOLATED Tool Execution (Engine-based for exec() support)
|
|
652
|
+
%% ============================================================
|
|
653
|
+
%%
|
|
654
|
+
%% Tools run in their own engine to support exec() calls.
|
|
655
|
+
%% TypeScript creates the engine, steps it, handles request_exec,
|
|
656
|
+
%% and collects the result when the engine finishes.
|
|
657
|
+
%% exec() results are posted via the SESSION's exec_result mechanism.
|
|
658
|
+
|
|
659
|
+
%% Tool engine dynamic predicates
|
|
660
|
+
:- dynamic tool_engine/2. % tool_engine(ToolEngineId, Engine)
|
|
661
|
+
|
|
662
|
+
%% create_tool_engine(+SessionId, +ToolName, +Args, -ToolEngineId)
|
|
663
|
+
%% Creates an engine to execute a DML tool. Returns a unique engine ID.
|
|
664
|
+
create_tool_engine(SessionId, ToolName, Args, ToolEngineId) :-
|
|
665
|
+
atom_string(ToolNameAtom, ToolName),
|
|
666
|
+
% Generate unique tool engine ID
|
|
667
|
+
gensym(tool_engine_, ToolEngineId),
|
|
668
|
+
( clause(SessionId:tool(ToolPattern), Body),
|
|
669
|
+
ToolPattern =.. [ToolNameAtom|PatternArgs]
|
|
670
|
+
-> length(PatternArgs, TotalArity),
|
|
671
|
+
length(Args, InputArity),
|
|
672
|
+
length(HeadArgs, TotalArity),
|
|
673
|
+
fill_args(HeadArgs, Args, InputArity),
|
|
674
|
+
ToolHead =.. [ToolNameAtom|HeadArgs],
|
|
675
|
+
ToolHead = ToolPattern,
|
|
676
|
+
(session_params(SessionId, Params) -> true ; Params = params{}),
|
|
677
|
+
InitialState = state{memory: [], params: Params, context_stack: [], depth: 0},
|
|
678
|
+
% Create goal that will yield the result
|
|
679
|
+
Goal = (
|
|
680
|
+
nb_setval(current_session_id, SessionId),
|
|
681
|
+
catch(
|
|
682
|
+
(mi_call(Body, InitialState, _FinalState),
|
|
683
|
+
extract_tool_result_simple(TotalArity, InputArity, HeadArgs, ToolResult),
|
|
684
|
+
engine_yield(tool_finished(ToolResult))),
|
|
685
|
+
Error,
|
|
686
|
+
(handle_tool_engine_error(Error, ErrResult),
|
|
687
|
+
engine_yield(tool_finished(ErrResult)))
|
|
688
|
+
)
|
|
689
|
+
),
|
|
690
|
+
engine_create(_, Goal, Engine),
|
|
691
|
+
assertz(tool_engine(ToolEngineId, Engine))
|
|
692
|
+
; % Tool not found - create engine that just yields error
|
|
693
|
+
engine_create(_, engine_yield(tool_finished(error{message: "Tool not found"})), Engine),
|
|
694
|
+
assertz(tool_engine(ToolEngineId, Engine))
|
|
695
|
+
).
|
|
696
|
+
|
|
697
|
+
%% handle_tool_engine_error(+Error, -Result)
|
|
698
|
+
handle_tool_engine_error('$answer_commit', true) :- !.
|
|
699
|
+
handle_tool_engine_error(Error, error{message: ErrMsg}) :-
|
|
700
|
+
format(atom(ErrMsg), '~w', [Error]).
|
|
701
|
+
|
|
702
|
+
%% step_tool_engine(+ToolEngineId, -Status, -Content, -Payload)
|
|
703
|
+
%% Steps the tool engine, similar to step_engine but for tools.
|
|
704
|
+
step_tool_engine(ToolEngineId, Status, Content, Payload) :-
|
|
705
|
+
( tool_engine(ToolEngineId, Engine)
|
|
706
|
+
-> catch(
|
|
707
|
+
( engine_next(Engine, Result)
|
|
708
|
+
-> process_tool_engine_result(Result, Status, Content, Payload)
|
|
709
|
+
; % Engine exhausted without yielding - treat as failure
|
|
710
|
+
Status = finished,
|
|
711
|
+
Content = '',
|
|
712
|
+
Payload = payload{result: false}
|
|
713
|
+
),
|
|
714
|
+
Error,
|
|
715
|
+
( format(atom(ErrMsg), 'Tool engine error: ~w', [Error]),
|
|
716
|
+
Status = error,
|
|
717
|
+
Content = ErrMsg,
|
|
718
|
+
Payload = none
|
|
719
|
+
)
|
|
720
|
+
)
|
|
721
|
+
; Status = error,
|
|
722
|
+
Content = 'No tool engine found',
|
|
723
|
+
Payload = none
|
|
724
|
+
).
|
|
725
|
+
|
|
726
|
+
%% process_tool_engine_result(+Result, -Status, -Content, -Payload)
|
|
727
|
+
process_tool_engine_result(tool_finished(ToolResult), finished, '', payload{result: ToolResult}) :- !.
|
|
728
|
+
process_tool_engine_result(request_exec(Tool, Args), request_exec, '',
|
|
729
|
+
payload{toolName: Tool, args: Args}) :- !.
|
|
730
|
+
process_tool_engine_result(request_agent_loop(Desc, Vars, Tools, Memory, ToolScope), request_agent_loop, '',
|
|
731
|
+
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: ToolScope}) :- !.
|
|
732
|
+
%% Legacy 4-arg format (no tool scope)
|
|
733
|
+
process_tool_engine_result(request_agent_loop(Desc, Vars, Tools, Memory), request_agent_loop, '',
|
|
734
|
+
payload{taskDescription: Desc, outputVars: Vars, userTools: Tools, memory: Memory, toolScope: none}) :- !.
|
|
735
|
+
process_tool_engine_result(output(Text), output, Text, none) :- !.
|
|
736
|
+
process_tool_engine_result(log(Text), log, Text, none) :- !.
|
|
737
|
+
process_tool_engine_result(Other, error, Msg, none) :-
|
|
738
|
+
format(atom(Msg), 'Unknown tool engine result: ~w', [Other]).
|
|
739
|
+
|
|
740
|
+
%% destroy_tool_engine(+ToolEngineId)
|
|
741
|
+
destroy_tool_engine(ToolEngineId) :-
|
|
742
|
+
( tool_engine(ToolEngineId, Engine)
|
|
743
|
+
-> catch(engine_destroy(Engine), _, true)
|
|
744
|
+
; true
|
|
745
|
+
),
|
|
746
|
+
retractall(tool_engine(ToolEngineId, _)).
|
|
747
|
+
|
|
748
|
+
%% post_tool_agent_result(+SessionId, +ToolEngineId, +Success, +Variables, +Messages)
|
|
749
|
+
%% Posts the result of a nested agent loop back to the tool engine.
|
|
750
|
+
%% Also stores in session_agent_result so the meta-interpreter can retrieve it.
|
|
751
|
+
:- dynamic tool_engine_agent_result/2. % tool_engine_agent_result(ToolEngineId, Result)
|
|
752
|
+
|
|
753
|
+
post_tool_agent_result(SessionId, ToolEngineId, Success, Variables, Messages) :-
|
|
754
|
+
Result = result{success: Success, variables: Variables, messages: Messages},
|
|
755
|
+
% Store in tool_engine_agent_result (for future use)
|
|
756
|
+
retractall(tool_engine_agent_result(ToolEngineId, _)),
|
|
757
|
+
assertz(tool_engine_agent_result(ToolEngineId, Result)),
|
|
758
|
+
% Also store in session_agent_result so task() in the meta-interpreter can read it
|
|
759
|
+
retractall(session_agent_result(SessionId, _)),
|
|
760
|
+
assertz(session_agent_result(SessionId, Result)),
|
|
761
|
+
% Signal the tool engine that agent result is ready
|
|
762
|
+
( tool_engine(ToolEngineId, Engine)
|
|
763
|
+
-> catch(engine_post(Engine, agent_done), _, true)
|
|
764
|
+
; true
|
|
765
|
+
).
|
|
766
|
+
|
|
767
|
+
%% Helper predicates (shared with old isolated execution)
|
|
768
|
+
|
|
769
|
+
%% extract_tool_result_simple(+TotalArity, +InputArity, +HeadArgs, -Result)
|
|
770
|
+
extract_tool_result_simple(TotalArity, InputArity, HeadArgs, Result) :-
|
|
771
|
+
( TotalArity > InputArity,
|
|
772
|
+
last(HeadArgs, LastArg),
|
|
773
|
+
nonvar(LastArg)
|
|
774
|
+
-> Result = LastArg
|
|
775
|
+
; Result = true
|
|
776
|
+
).
|
|
777
|
+
|
|
778
|
+
%% fill_args(+HeadArgs, +InputArgs, +Count)
|
|
779
|
+
%% Fill the first Count elements of HeadArgs with InputArgs
|
|
780
|
+
fill_args(_, [], 0) :- !.
|
|
781
|
+
fill_args([H|T], [A|As], N) :-
|
|
782
|
+
N > 0,
|
|
783
|
+
H = A,
|
|
784
|
+
N1 is N - 1,
|
|
785
|
+
fill_args(T, As, N1).
|
|
786
|
+
|
|
787
|
+
%% bind_tool_params(+ParamsList, +ArgsDict)
|
|
788
|
+
%% Binds tool parameters from the arguments dictionary
|
|
789
|
+
bind_tool_params([], _) :- !.
|
|
790
|
+
bind_tool_params([Param|Rest], Args) :-
|
|
791
|
+
( atom(Param)
|
|
792
|
+
-> ParamName = Param, ParamVar = Param
|
|
793
|
+
; Param = (ParamName = ParamVar)
|
|
794
|
+
),
|
|
795
|
+
( get_dict(ParamName, Args, Value)
|
|
796
|
+
-> ParamVar = Value
|
|
797
|
+
; true % Leave unbound if not provided
|
|
798
|
+
),
|
|
799
|
+
bind_tool_params(Rest, Args).
|
|
800
|
+
|
|
801
|
+
%% ============================================================
|
|
802
|
+
%% Task Handling
|
|
803
|
+
%% ============================================================
|
|
804
|
+
|
|
805
|
+
%% collect_user_tools(+SessionId, -ToolSchemas)
|
|
806
|
+
collect_user_tools(SessionId, ToolSchemas) :-
|
|
807
|
+
findall(
|
|
808
|
+
tool_info{name: Name, schema: Schema, source: Source},
|
|
809
|
+
(
|
|
810
|
+
session_user_tool_schema(SessionId, Name, Schema),
|
|
811
|
+
(SessionId:tool_source(Name, Source) -> true ; Source = "")
|
|
812
|
+
),
|
|
813
|
+
ToolSchemas
|
|
814
|
+
).
|
|
815
|
+
|
|
816
|
+
%% mi_call(task(Desc), +StateIn, -StateOut)
|
|
817
|
+
mi_call(task(Desc), StateIn, StateOut) :-
|
|
818
|
+
!,
|
|
819
|
+
get_params(StateIn, Params),
|
|
820
|
+
interpolate_desc(Desc, Params, InterpDesc),
|
|
821
|
+
get_session_id(SessionId),
|
|
822
|
+
get_depth(StateIn, Depth),
|
|
823
|
+
add_trace_entry(SessionId, llm_call, task, [InterpDesc], Depth),
|
|
824
|
+
collect_user_tools(SessionId, UserTools),
|
|
825
|
+
get_memory(StateIn, Memory),
|
|
826
|
+
get_tool_scope(StateIn, ToolScope),
|
|
827
|
+
% Yield request with current memory and tool scope
|
|
828
|
+
engine_yield(request_agent_loop(InterpDesc, [], UserTools, Memory, ToolScope)),
|
|
829
|
+
% Handle signals (tool calls) until agent_done
|
|
830
|
+
handle_agent_signals(SessionId, StateIn, StateAfterAgent),
|
|
831
|
+
session_agent_result(SessionId, Result),
|
|
832
|
+
retract(session_agent_result(SessionId, _)),
|
|
833
|
+
% Check success and warn on failure
|
|
834
|
+
( Result.success == true
|
|
835
|
+
-> true
|
|
836
|
+
; ( get_dict(error, Result, ErrorMsg), ErrorMsg \= ""
|
|
837
|
+
-> format(atom(WarnMsg), 'Warning: task/1 failed: ~w', [ErrorMsg])
|
|
838
|
+
; format(atom(WarnMsg), 'Warning: task/1 failed (success=false)', [])
|
|
839
|
+
),
|
|
840
|
+
engine_yield(output(WarnMsg)),
|
|
841
|
+
fail
|
|
842
|
+
),
|
|
843
|
+
% Use full messages from agent loop result
|
|
844
|
+
( get_dict(messages, Result, Messages), Messages \= []
|
|
845
|
+
-> % Replace memory with all messages from agent (includes system, history, and new messages)
|
|
846
|
+
set_memory(StateAfterAgent, Messages, StateOut)
|
|
847
|
+
; % Fallback: just add task description and response (old behavior)
|
|
848
|
+
add_memory(StateAfterAgent, user, InterpDesc, State1),
|
|
849
|
+
( get_dict(response, Result, Response), Response \= ""
|
|
850
|
+
-> add_trace_with_result(SessionId, exit, task, [InterpDesc], Response, Depth),
|
|
851
|
+
add_memory(State1, assistant, Response, StateOut)
|
|
852
|
+
; add_trace_entry(SessionId, exit, task, [InterpDesc], Depth),
|
|
853
|
+
StateOut = State1
|
|
854
|
+
)
|
|
855
|
+
).
|
|
856
|
+
|
|
857
|
+
%% mi_call(task_named(Desc, Vars, VarNames), +StateIn, -StateOut)
|
|
858
|
+
%% New unified handler for task/N with embedded variable names
|
|
859
|
+
mi_call(task_named(Desc, Vars, VarNames), StateIn, StateOut) :-
|
|
860
|
+
!,
|
|
861
|
+
mi_call_task_n(Desc, Vars, VarNames, StateIn, StateOut).
|
|
862
|
+
|
|
863
|
+
%% ============================================================
|
|
864
|
+
%% Prompt Handling - Fresh LLM call without existing memory
|
|
865
|
+
%% ============================================================
|
|
866
|
+
|
|
867
|
+
%% mi_call(prompt(Desc), +StateIn, -StateOut)
|
|
868
|
+
%% Like task/1 but starts with empty memory
|
|
869
|
+
mi_call(prompt(Desc), StateIn, StateOut) :-
|
|
870
|
+
!,
|
|
871
|
+
get_params(StateIn, Params),
|
|
872
|
+
interpolate_desc(Desc, Params, InterpDesc),
|
|
873
|
+
get_session_id(SessionId),
|
|
874
|
+
collect_user_tools(SessionId, UserTools),
|
|
875
|
+
get_tool_scope(StateIn, ToolScope),
|
|
876
|
+
% Use empty memory instead of current memory
|
|
877
|
+
engine_yield(request_agent_loop(InterpDesc, [], UserTools, [], ToolScope)),
|
|
878
|
+
% Handle signals (tool calls) until agent_done - still need for tool execution
|
|
879
|
+
handle_agent_signals(SessionId, StateIn, StateOut),
|
|
880
|
+
session_agent_result(SessionId, Result),
|
|
881
|
+
retract(session_agent_result(SessionId, _)),
|
|
882
|
+
% Check success and warn on failure
|
|
883
|
+
( Result.success == true
|
|
884
|
+
-> true
|
|
885
|
+
; ( get_dict(error, Result, ErrorMsg), ErrorMsg \= ""
|
|
886
|
+
-> format(atom(WarnMsg), 'Warning: prompt/1 failed: ~w', [ErrorMsg])
|
|
887
|
+
; format(atom(WarnMsg), 'Warning: prompt/1 failed (success=false)', [])
|
|
888
|
+
),
|
|
889
|
+
engine_yield(output(WarnMsg)),
|
|
890
|
+
fail
|
|
891
|
+
).
|
|
892
|
+
% Don't modify memory - but state may have changed from tool calls
|
|
893
|
+
|
|
894
|
+
%% mi_call(prompt_named(Desc, Vars, VarNames), +StateIn, -StateOut)
|
|
895
|
+
%% New unified handler for prompt/N with embedded variable names
|
|
896
|
+
mi_call(prompt_named(Desc, Vars, VarNames), StateIn, StateOut) :-
|
|
897
|
+
!,
|
|
898
|
+
mi_call_prompt_n(Desc, Vars, VarNames, StateIn, StateOut).
|
|
899
|
+
|
|
900
|
+
%% mi_call_prompt_n(+Desc, +Vars, +VarNames, +StateIn, -StateOut)
|
|
901
|
+
%% Like mi_call_task_n but uses empty memory and doesn't update memory
|
|
902
|
+
mi_call_prompt_n(Desc, Vars, VarNames, StateIn, StateOut) :-
|
|
903
|
+
!, % Cut to make this predicate deterministic - no backtracking once started
|
|
904
|
+
get_params(StateIn, Params),
|
|
905
|
+
interpolate_desc(Desc, Params, InterpDesc),
|
|
906
|
+
get_session_id(SessionId),
|
|
907
|
+
collect_user_tools(SessionId, UserTools),
|
|
908
|
+
get_tool_scope(StateIn, ToolScope),
|
|
909
|
+
% Use empty memory instead of current memory
|
|
910
|
+
engine_yield(request_agent_loop(InterpDesc, VarNames, UserTools, [], ToolScope)),
|
|
911
|
+
% Handle signals (tool calls) until agent_done - still need for tool execution
|
|
912
|
+
handle_agent_signals(SessionId, StateIn, StateOut),
|
|
913
|
+
session_agent_result(SessionId, Result),
|
|
914
|
+
retract(session_agent_result(SessionId, _)),
|
|
915
|
+
% Check success and warn on failure
|
|
916
|
+
( Result.success == true
|
|
917
|
+
-> true
|
|
918
|
+
; length(VarNames, Arity),
|
|
919
|
+
ActualArity is Arity + 1,
|
|
920
|
+
( get_dict(error, Result, ErrorMsg), ErrorMsg \= ""
|
|
921
|
+
-> format(atom(WarnMsg), 'Warning: prompt/~w failed: ~w', [ActualArity, ErrorMsg])
|
|
922
|
+
; format(atom(WarnMsg), 'Warning: prompt/~w failed (success=false)', [ActualArity])
|
|
923
|
+
),
|
|
924
|
+
engine_yield(output(WarnMsg)),
|
|
925
|
+
fail
|
|
926
|
+
),
|
|
927
|
+
bind_task_variables(Result.variables, VarNames, Vars).
|
|
928
|
+
% Don't modify memory - but state may have changed from tool calls
|
|
929
|
+
|
|
930
|
+
%% mi_call_task_n(+Desc, +Vars, +VarNames, +StateIn, -StateOut)
|
|
931
|
+
mi_call_task_n(Desc, Vars, VarNames, StateIn, StateOut) :-
|
|
932
|
+
!, % Cut to make this predicate deterministic - no backtracking once started
|
|
933
|
+
get_params(StateIn, Params),
|
|
934
|
+
interpolate_desc(Desc, Params, InterpDesc),
|
|
935
|
+
get_session_id(SessionId),
|
|
936
|
+
collect_user_tools(SessionId, UserTools),
|
|
937
|
+
get_memory(StateIn, Memory),
|
|
938
|
+
get_tool_scope(StateIn, ToolScope),
|
|
939
|
+
engine_yield(request_agent_loop(InterpDesc, VarNames, UserTools, Memory, ToolScope)),
|
|
940
|
+
% Handle signals (tool calls) until agent_done
|
|
941
|
+
handle_agent_signals(SessionId, StateIn, StateAfterAgent),
|
|
942
|
+
session_agent_result(SessionId, Result),
|
|
943
|
+
retract(session_agent_result(SessionId, _)),
|
|
944
|
+
% Check success and warn on failure
|
|
945
|
+
( Result.success == true
|
|
946
|
+
-> true
|
|
947
|
+
; length(VarNames, Arity),
|
|
948
|
+
ActualArity is Arity + 1,
|
|
949
|
+
( get_dict(error, Result, ErrorMsg), ErrorMsg \= ""
|
|
950
|
+
-> format(atom(WarnMsg), 'Warning: task/~w failed: ~w', [ActualArity, ErrorMsg])
|
|
951
|
+
; format(atom(WarnMsg), 'Warning: task/~w failed (success=false)', [ActualArity])
|
|
952
|
+
),
|
|
953
|
+
engine_yield(output(WarnMsg)),
|
|
954
|
+
fail
|
|
955
|
+
),
|
|
956
|
+
bind_task_variables(Result.variables, VarNames, Vars),
|
|
957
|
+
% Use full messages from agent loop result
|
|
958
|
+
( get_dict(messages, Result, Messages), Messages \= []
|
|
959
|
+
-> % Replace memory with all messages from agent
|
|
960
|
+
set_memory(StateAfterAgent, Messages, StateOut)
|
|
961
|
+
; % Fallback: just add task description and response (old behavior)
|
|
962
|
+
add_memory(StateAfterAgent, user, InterpDesc, State1),
|
|
963
|
+
( get_dict(response, Result, Response), Response \= ""
|
|
964
|
+
-> add_memory(State1, assistant, Response, StateOut)
|
|
965
|
+
; StateOut = State1
|
|
966
|
+
)
|
|
967
|
+
).
|
|
968
|
+
|
|
969
|
+
%% bind_task_variables(+VarsDict, +Names, -Values)
|
|
970
|
+
bind_task_variables(_, [], []) :- !.
|
|
971
|
+
bind_task_variables(VarsDict, [Name|Names], [Value|Values]) :-
|
|
972
|
+
( get_dict(Name, VarsDict, Value)
|
|
973
|
+
-> true
|
|
974
|
+
; true
|
|
975
|
+
),
|
|
976
|
+
bind_task_variables(VarsDict, Names, Values).
|
|
977
|
+
|
|
978
|
+
%% ============================================================
|
|
979
|
+
%% Exec Handling
|
|
980
|
+
%% ============================================================
|
|
981
|
+
|
|
982
|
+
%% mi_call(exec(ToolCall, Output), +StateIn, -StateOut)
|
|
983
|
+
mi_call(exec(ToolCall, Output), StateIn, StateIn) :-
|
|
984
|
+
!,
|
|
985
|
+
ToolCall =.. [ToolName|Args],
|
|
986
|
+
get_session_id(SessionId),
|
|
987
|
+
get_depth(StateIn, Depth),
|
|
988
|
+
add_trace_entry(SessionId, exec, ToolName, Args, Depth),
|
|
989
|
+
engine_yield(request_exec(ToolName, Args)),
|
|
990
|
+
% Use get_next_signal helper which handles both dynamic predicate and engine_fetch
|
|
991
|
+
get_next_signal(SessionId, _Signal),
|
|
992
|
+
session_exec_result(SessionId, Result),
|
|
993
|
+
retract(session_exec_result(SessionId, _)),
|
|
994
|
+
( Result.status == success
|
|
995
|
+
-> Output = Result.result,
|
|
996
|
+
add_trace_with_result(SessionId, exit, ToolName, Args, Output, Depth)
|
|
997
|
+
; add_trace_entry(SessionId, fail, ToolName, Args, Depth),
|
|
998
|
+
fail
|
|
999
|
+
).
|
|
1000
|
+
|
|
1001
|
+
%% ============================================================
|
|
1002
|
+
%% Tool Scoping Predicates
|
|
1003
|
+
%% ============================================================
|
|
1004
|
+
%% These predicates allow manual control over which tools are available
|
|
1005
|
+
%% to nested task() calls. They set a scope in the state which is passed
|
|
1006
|
+
%% to the TypeScript runner and used to filter available tools.
|
|
1007
|
+
%%
|
|
1008
|
+
%% Example:
|
|
1009
|
+
%% tool(smart_format(Items, Output)) :-
|
|
1010
|
+
%% with_tools([summarize, calculate], (
|
|
1011
|
+
%% task("Format this nicely", Output)
|
|
1012
|
+
%% )).
|
|
1013
|
+
|
|
1014
|
+
%% mi_call(with_tools(ToolList, Goal), +StateIn, -StateOut)
|
|
1015
|
+
%% Run Goal with only the specified tools available to nested tasks
|
|
1016
|
+
mi_call(with_tools(ToolList, Goal), StateIn, StateOut) :-
|
|
1017
|
+
!,
|
|
1018
|
+
set_tool_scope(StateIn, whitelist(ToolList), ScopedState),
|
|
1019
|
+
mi_call(Goal, ScopedState, StateWithScope),
|
|
1020
|
+
clear_tool_scope(StateWithScope, StateOut).
|
|
1021
|
+
|
|
1022
|
+
%% mi_call(without_tools(ToolList, Goal), +StateIn, -StateOut)
|
|
1023
|
+
%% Run Goal with the specified tools excluded from nested tasks
|
|
1024
|
+
mi_call(without_tools(ToolList, Goal), StateIn, StateOut) :-
|
|
1025
|
+
!,
|
|
1026
|
+
set_tool_scope(StateIn, blacklist(ToolList), ScopedState),
|
|
1027
|
+
mi_call(Goal, ScopedState, StateWithScope),
|
|
1028
|
+
clear_tool_scope(StateWithScope, StateOut).
|
|
1029
|
+
|
|
1030
|
+
%% ============================================================
|
|
1031
|
+
%% Memory Predicates - NOW BACKTRACKABLE VIA STATE!
|
|
1032
|
+
%% ============================================================
|
|
1033
|
+
|
|
1034
|
+
%% mi_call(system(Text), +StateIn, -StateOut)
|
|
1035
|
+
mi_call(system(Text), StateIn, StateOut) :-
|
|
1036
|
+
!,
|
|
1037
|
+
get_params(StateIn, Params),
|
|
1038
|
+
interpolate_desc(Text, Params, InterpText),
|
|
1039
|
+
add_memory(StateIn, system, InterpText, StateOut).
|
|
1040
|
+
|
|
1041
|
+
%% mi_call(user(Text), +StateIn, -StateOut)
|
|
1042
|
+
mi_call(user(Text), StateIn, StateOut) :-
|
|
1043
|
+
!,
|
|
1044
|
+
get_params(StateIn, Params),
|
|
1045
|
+
interpolate_desc(Text, Params, InterpText),
|
|
1046
|
+
add_memory(StateIn, user, InterpText, StateOut).
|
|
1047
|
+
|
|
1048
|
+
%% ============================================================
|
|
1049
|
+
%% Output Predicates
|
|
1050
|
+
%% ============================================================
|
|
1051
|
+
|
|
1052
|
+
%% mi_call(answer(Text), +StateIn, -StateOut)
|
|
1053
|
+
%% answer/1 commits by throwing - prevents backtracking to other clauses
|
|
1054
|
+
mi_call(answer(Text), StateIn, StateIn) :-
|
|
1055
|
+
get_params(StateIn, Params),
|
|
1056
|
+
interpolate_desc(Text, Params, InterpText),
|
|
1057
|
+
engine_yield(answer(InterpText)),
|
|
1058
|
+
throw('$answer_commit').
|
|
1059
|
+
|
|
1060
|
+
%% mi_call(output(Text), +StateIn, -StateOut)
|
|
1061
|
+
mi_call(output(Text), StateIn, StateIn) :-
|
|
1062
|
+
get_params(StateIn, Params),
|
|
1063
|
+
interpolate_desc(Text, Params, InterpText),
|
|
1064
|
+
get_session_id(SessionId),
|
|
1065
|
+
get_depth(StateIn, Depth),
|
|
1066
|
+
add_trace_entry(SessionId, output, output, [InterpText], Depth),
|
|
1067
|
+
engine_yield(output(InterpText)).
|
|
1068
|
+
|
|
1069
|
+
%% mi_call(input(Prompt, Input), +StateIn, -StateOut)
|
|
1070
|
+
%% Request input from the user with a prompt
|
|
1071
|
+
mi_call(input(Prompt, Input), StateIn, StateIn) :-
|
|
1072
|
+
get_params(StateIn, Params),
|
|
1073
|
+
interpolate_desc(Prompt, Params, InterpPrompt),
|
|
1074
|
+
get_session_id(SessionId),
|
|
1075
|
+
get_depth(StateIn, Depth),
|
|
1076
|
+
add_trace_entry(SessionId, input, input, [InterpPrompt], Depth),
|
|
1077
|
+
engine_yield(wait_input(InterpPrompt)),
|
|
1078
|
+
% Use get_next_signal helper which handles both dynamic predicate and engine_fetch
|
|
1079
|
+
get_next_signal(SessionId, _Signal),
|
|
1080
|
+
% Retrieve the input provided via provide_input/2
|
|
1081
|
+
( session_pending_input(SessionId, Input)
|
|
1082
|
+
-> retract(session_pending_input(SessionId, Input)),
|
|
1083
|
+
add_trace_with_result(SessionId, exit, input, [InterpPrompt], Input, Depth)
|
|
1084
|
+
; Input = "", % Default to empty if no input provided
|
|
1085
|
+
add_trace_with_result(SessionId, exit, input, [InterpPrompt], "", Depth)
|
|
1086
|
+
).
|
|
1087
|
+
|
|
1088
|
+
%% mi_call(yield(Text), +StateIn, -StateOut)
|
|
1089
|
+
mi_call(yield(Text), StateIn, StateIn) :-
|
|
1090
|
+
get_params(StateIn, Params),
|
|
1091
|
+
interpolate_desc(Text, Params, InterpText),
|
|
1092
|
+
get_session_id(SessionId),
|
|
1093
|
+
get_depth(StateIn, Depth),
|
|
1094
|
+
add_trace_entry(SessionId, output, yield, [InterpText], Depth),
|
|
1095
|
+
engine_yield(output(InterpText)).
|
|
1096
|
+
|
|
1097
|
+
%% mi_call(log(Text), +StateIn, -StateOut)
|
|
1098
|
+
mi_call(log(Text), StateIn, StateIn) :-
|
|
1099
|
+
get_params(StateIn, Params),
|
|
1100
|
+
interpolate_desc(Text, Params, InterpText),
|
|
1101
|
+
engine_yield(log(InterpText)).
|
|
1102
|
+
|
|
1103
|
+
%% ============================================================
|
|
1104
|
+
%% Context Stack Management - for memory save/restore
|
|
1105
|
+
%% ============================================================
|
|
1106
|
+
|
|
1107
|
+
%% mi_call(push_context, +StateIn, -StateOut)
|
|
1108
|
+
%% Save current memory to stack, keep memory active
|
|
1109
|
+
mi_call(push_context, StateIn, StateOut) :-
|
|
1110
|
+
!,
|
|
1111
|
+
get_memory(StateIn, Memory),
|
|
1112
|
+
get_context_stack(StateIn, Stack),
|
|
1113
|
+
set_context_stack(StateIn, [Memory|Stack], StateOut).
|
|
1114
|
+
|
|
1115
|
+
%% mi_call(push_context(clear), +StateIn, -StateOut)
|
|
1116
|
+
%% Save current memory to stack AND clear memory
|
|
1117
|
+
mi_call(push_context(clear), StateIn, StateOut) :-
|
|
1118
|
+
!,
|
|
1119
|
+
get_memory(StateIn, Memory),
|
|
1120
|
+
get_context_stack(StateIn, Stack),
|
|
1121
|
+
set_context_stack(StateIn, [Memory|Stack], State1),
|
|
1122
|
+
set_memory(State1, [], StateOut).
|
|
1123
|
+
|
|
1124
|
+
%% mi_call(pop_context, +StateIn, -StateOut)
|
|
1125
|
+
%% Restore memory from stack
|
|
1126
|
+
mi_call(pop_context, StateIn, StateOut) :-
|
|
1127
|
+
!,
|
|
1128
|
+
get_context_stack(StateIn, Stack),
|
|
1129
|
+
( Stack = [SavedMemory|RestStack]
|
|
1130
|
+
-> set_memory(StateIn, SavedMemory, State1),
|
|
1131
|
+
set_context_stack(State1, RestStack, StateOut)
|
|
1132
|
+
; % Empty stack - do nothing
|
|
1133
|
+
StateOut = StateIn
|
|
1134
|
+
).
|
|
1135
|
+
|
|
1136
|
+
%% mi_call(clear_memory, +StateIn, -StateOut)
|
|
1137
|
+
%% Clear current memory without saving
|
|
1138
|
+
mi_call(clear_memory, StateIn, StateOut) :-
|
|
1139
|
+
!,
|
|
1140
|
+
set_memory(StateIn, [], StateOut).
|
|
1141
|
+
|
|
1142
|
+
%% ============================================================
|
|
1143
|
+
%% Parameter Handling
|
|
1144
|
+
%% ============================================================
|
|
1145
|
+
|
|
1146
|
+
%% mi_call(param(Key, Value), +StateIn, -StateOut)
|
|
1147
|
+
%% Simple param/2 lookup from session module (asserted facts)
|
|
1148
|
+
mi_call(param(Key, Value), StateIn, StateIn) :-
|
|
1149
|
+
!,
|
|
1150
|
+
get_session_id(SessionId),
|
|
1151
|
+
( atom(Key)
|
|
1152
|
+
-> KeyAtom = Key
|
|
1153
|
+
; atom_string(KeyAtom, Key)
|
|
1154
|
+
),
|
|
1155
|
+
SessionId:param(KeyAtom, Value).
|
|
1156
|
+
|
|
1157
|
+
%% mi_call(param(Key, Desc, Value), +StateIn, -StateOut)
|
|
1158
|
+
%% Legacy param/3 - still queries from state params dict
|
|
1159
|
+
mi_call(param(Key, _Desc, Value), StateIn, StateIn) :-
|
|
1160
|
+
!,
|
|
1161
|
+
get_params(StateIn, Params),
|
|
1162
|
+
( atom(Key)
|
|
1163
|
+
-> KeyAtom = Key
|
|
1164
|
+
; atom_string(KeyAtom, Key)
|
|
1165
|
+
),
|
|
1166
|
+
get_dict(KeyAtom, Params, Value).
|
|
1167
|
+
|
|
1168
|
+
%% ============================================================
|
|
1169
|
+
%% Control Flow - STATE THREADED
|
|
1170
|
+
%% No cuts - rely on is_mi_special_predicate guard in catch-all
|
|
1171
|
+
%% ============================================================
|
|
1172
|
+
|
|
1173
|
+
%% mi_call((A, B), +StateIn, -StateOut)
|
|
1174
|
+
mi_call((A, B), StateIn, StateOut) :-
|
|
1175
|
+
mi_call(A, StateIn, State1),
|
|
1176
|
+
mi_call(B, State1, StateOut).
|
|
1177
|
+
|
|
1178
|
+
%% mi_call((Cond -> Then ; Else), +StateIn, -StateOut)
|
|
1179
|
+
%% MUST come before disjunction clause - if-then-else is syntactically a disjunction!
|
|
1180
|
+
mi_call((Cond -> Then ; Else), StateIn, StateOut) :-
|
|
1181
|
+
!, % Commit to this clause for if-then-else patterns
|
|
1182
|
+
( mi_call(Cond, StateIn, State1)
|
|
1183
|
+
-> mi_call(Then, State1, StateOut)
|
|
1184
|
+
; mi_call(Else, StateIn, StateOut)
|
|
1185
|
+
).
|
|
1186
|
+
|
|
1187
|
+
%% mi_call((Cond -> Then), +StateIn, -StateOut)
|
|
1188
|
+
%% Soft cut without else branch
|
|
1189
|
+
mi_call((Cond -> Then), StateIn, StateOut) :-
|
|
1190
|
+
!, % Commit to this clause
|
|
1191
|
+
( mi_call(Cond, StateIn, State1)
|
|
1192
|
+
-> mi_call(Then, State1, StateOut)
|
|
1193
|
+
).
|
|
1194
|
+
|
|
1195
|
+
%% mi_call((A ; B), +StateIn, -StateOut)
|
|
1196
|
+
%% Disjunction - BACKTRACKABLE! On failure, StateIn is restored
|
|
1197
|
+
%% NOTE: This clause must come AFTER if-then-else clauses
|
|
1198
|
+
mi_call((A ; B), StateIn, StateOut) :-
|
|
1199
|
+
( mi_call(A, StateIn, StateOut)
|
|
1200
|
+
; mi_call(B, StateIn, StateOut)
|
|
1201
|
+
).
|
|
1202
|
+
|
|
1203
|
+
%% mi_call(\+(Goal), +StateIn, -StateOut)
|
|
1204
|
+
mi_call(\+(Goal), StateIn, StateIn) :-
|
|
1205
|
+
\+ mi_call(Goal, StateIn, _).
|
|
1206
|
+
|
|
1207
|
+
%% mi_call(!, +StateIn, -StateOut)
|
|
1208
|
+
%% Cut DOES need to actually cut
|
|
1209
|
+
mi_call(!, StateIn, StateIn) :-
|
|
1210
|
+
!.
|
|
1211
|
+
|
|
1212
|
+
%% mi_call(true, +StateIn, -StateOut)
|
|
1213
|
+
mi_call(true, StateIn, StateIn).
|
|
1214
|
+
|
|
1215
|
+
%% mi_call(fail, +StateIn, -StateOut)
|
|
1216
|
+
mi_call(fail, _StateIn, _StateOut) :-
|
|
1217
|
+
fail.
|
|
1218
|
+
|
|
1219
|
+
%% mi_call(false, +StateIn, -StateOut)
|
|
1220
|
+
mi_call(false, _StateIn, _StateOut) :-
|
|
1221
|
+
fail.
|
|
1222
|
+
|
|
1223
|
+
%% ============================================================
|
|
1224
|
+
%% List Predicates - Interpreted for proper state threading
|
|
1225
|
+
%% ============================================================
|
|
1226
|
+
|
|
1227
|
+
%% mi_call(member(X, List), +StateIn, -StateOut)
|
|
1228
|
+
%% List membership - allows backtracking through list elements
|
|
1229
|
+
mi_call(member(X, List), StateIn, StateIn) :-
|
|
1230
|
+
member(X, List).
|
|
1231
|
+
|
|
1232
|
+
%% mi_call(append(A, B, C), +StateIn, -StateOut)
|
|
1233
|
+
mi_call(append(A, B, C), StateIn, StateIn) :-
|
|
1234
|
+
append(A, B, C).
|
|
1235
|
+
|
|
1236
|
+
%% mi_call(length(List, Len), +StateIn, -StateOut)
|
|
1237
|
+
mi_call(length(List, Len), StateIn, StateIn) :-
|
|
1238
|
+
length(List, Len).
|
|
1239
|
+
|
|
1240
|
+
%% mi_call(nth0(Index, List, Elem), +StateIn, -StateOut)
|
|
1241
|
+
mi_call(nth0(Index, List, Elem), StateIn, StateIn) :-
|
|
1242
|
+
nth0(Index, List, Elem).
|
|
1243
|
+
|
|
1244
|
+
%% mi_call(nth1(Index, List, Elem), +StateIn, -StateOut)
|
|
1245
|
+
mi_call(nth1(Index, List, Elem), StateIn, StateIn) :-
|
|
1246
|
+
nth1(Index, List, Elem).
|
|
1247
|
+
|
|
1248
|
+
%% mi_call(last(List, Elem), +StateIn, -StateOut)
|
|
1249
|
+
mi_call(last(List, Elem), StateIn, StateIn) :-
|
|
1250
|
+
last(List, Elem).
|
|
1251
|
+
|
|
1252
|
+
%% mi_call(reverse(List, Reversed), +StateIn, -StateOut)
|
|
1253
|
+
mi_call(reverse(List, Reversed), StateIn, StateIn) :-
|
|
1254
|
+
reverse(List, Reversed).
|
|
1255
|
+
|
|
1256
|
+
%% mi_call(sort(List, Sorted), +StateIn, -StateOut)
|
|
1257
|
+
mi_call(sort(List, Sorted), StateIn, StateIn) :-
|
|
1258
|
+
sort(List, Sorted).
|
|
1259
|
+
|
|
1260
|
+
%% mi_call(msort(List, Sorted), +StateIn, -StateOut)
|
|
1261
|
+
mi_call(msort(List, Sorted), StateIn, StateIn) :-
|
|
1262
|
+
msort(List, Sorted).
|
|
1263
|
+
|
|
1264
|
+
%% ============================================================
|
|
1265
|
+
%% Meta-Predicates - Interpreted with proper state threading
|
|
1266
|
+
%% ============================================================
|
|
1267
|
+
|
|
1268
|
+
%% mi_call(include(Goal, List, Included), +StateIn, -StateOut)
|
|
1269
|
+
%% Filter list keeping elements where Goal succeeds
|
|
1270
|
+
mi_call(include(Goal, List, Included), StateIn, StateOut) :-
|
|
1271
|
+
mi_include(Goal, List, Included, StateIn, StateOut).
|
|
1272
|
+
|
|
1273
|
+
mi_include(_, [], [], State, State).
|
|
1274
|
+
mi_include(Goal, [H|T], Result, StateIn, StateOut) :-
|
|
1275
|
+
copy_term(Goal, GoalCopy),
|
|
1276
|
+
GoalCopy =.. GoalList,
|
|
1277
|
+
append(GoalList, [H], GoalWithArg),
|
|
1278
|
+
TestGoal =.. GoalWithArg,
|
|
1279
|
+
( mi_call(TestGoal, StateIn, State1)
|
|
1280
|
+
-> Result = [H|Rest],
|
|
1281
|
+
mi_include(Goal, T, Rest, State1, StateOut)
|
|
1282
|
+
; mi_include(Goal, T, Result, StateIn, StateOut)
|
|
1283
|
+
).
|
|
1284
|
+
|
|
1285
|
+
%% mi_call(exclude(Goal, List, Excluded), +StateIn, -StateOut)
|
|
1286
|
+
%% Filter list removing elements where Goal succeeds
|
|
1287
|
+
mi_call(exclude(Goal, List, Excluded), StateIn, StateOut) :-
|
|
1288
|
+
mi_exclude(Goal, List, Excluded, StateIn, StateOut).
|
|
1289
|
+
|
|
1290
|
+
mi_exclude(_, [], [], State, State).
|
|
1291
|
+
mi_exclude(Goal, [H|T], Result, StateIn, StateOut) :-
|
|
1292
|
+
copy_term(Goal, GoalCopy),
|
|
1293
|
+
GoalCopy =.. GoalList,
|
|
1294
|
+
append(GoalList, [H], GoalWithArg),
|
|
1295
|
+
TestGoal =.. GoalWithArg,
|
|
1296
|
+
( mi_call(TestGoal, StateIn, State1)
|
|
1297
|
+
-> mi_exclude(Goal, T, Result, State1, StateOut)
|
|
1298
|
+
; Result = [H|Rest],
|
|
1299
|
+
mi_exclude(Goal, T, Rest, StateIn, StateOut)
|
|
1300
|
+
).
|
|
1301
|
+
|
|
1302
|
+
%% mi_call(partition(Goal, List, Included, Excluded), +StateIn, -StateOut)
|
|
1303
|
+
%% Partition list into elements where Goal succeeds and fails
|
|
1304
|
+
mi_call(partition(Goal, List, Included, Excluded), StateIn, StateOut) :-
|
|
1305
|
+
mi_partition(Goal, List, Included, Excluded, StateIn, StateOut).
|
|
1306
|
+
|
|
1307
|
+
mi_partition(_, [], [], [], State, State).
|
|
1308
|
+
mi_partition(Goal, [H|T], Inc, Exc, StateIn, StateOut) :-
|
|
1309
|
+
copy_term(Goal, GoalCopy),
|
|
1310
|
+
GoalCopy =.. GoalList,
|
|
1311
|
+
append(GoalList, [H], GoalWithArg),
|
|
1312
|
+
TestGoal =.. GoalWithArg,
|
|
1313
|
+
( mi_call(TestGoal, StateIn, State1)
|
|
1314
|
+
-> Inc = [H|IncRest],
|
|
1315
|
+
mi_partition(Goal, T, IncRest, Exc, State1, StateOut)
|
|
1316
|
+
; Exc = [H|ExcRest],
|
|
1317
|
+
mi_partition(Goal, T, Inc, ExcRest, StateIn, StateOut)
|
|
1318
|
+
).
|
|
1319
|
+
|
|
1320
|
+
%% mi_call(forall(Cond, Action), +StateIn, -StateOut)
|
|
1321
|
+
%% Succeed if for all solutions of Cond, Action succeeds
|
|
1322
|
+
%% Note: State changes in Action are NOT preserved (bagof semantics)
|
|
1323
|
+
mi_call(forall(Cond, Action), StateIn, StateIn) :-
|
|
1324
|
+
\+ (mi_call(Cond, StateIn, State1), \+ mi_call(Action, State1, _)).
|
|
1325
|
+
|
|
1326
|
+
%% mi_call(findall(Template, Goal, List), +StateIn, -StateOut)
|
|
1327
|
+
%% Collect all solutions - state changes are NOT preserved
|
|
1328
|
+
mi_call(findall(Template, Goal, List), StateIn, StateIn) :-
|
|
1329
|
+
findall(Template, mi_call(Goal, StateIn, _), List).
|
|
1330
|
+
|
|
1331
|
+
%% mi_call(bagof(Template, Goal, List), +StateIn, -StateOut)
|
|
1332
|
+
mi_call(bagof(Template, Goal, List), StateIn, StateIn) :-
|
|
1333
|
+
bagof(Template, mi_call(Goal, StateIn, _), List).
|
|
1334
|
+
|
|
1335
|
+
%% mi_call(setof(Template, Goal, List), +StateIn, -StateOut)
|
|
1336
|
+
mi_call(setof(Template, Goal, List), StateIn, StateIn) :-
|
|
1337
|
+
setof(Template, mi_call(Goal, StateIn, _), List).
|
|
1338
|
+
|
|
1339
|
+
%% mi_call(aggregate_all(Template, Goal, Result), +StateIn, -StateOut)
|
|
1340
|
+
mi_call(aggregate_all(Template, Goal, Result), StateIn, StateIn) :-
|
|
1341
|
+
aggregate_all(Template, mi_call(Goal, StateIn, _), Result).
|
|
1342
|
+
|
|
1343
|
+
%% mi_call(maplist(Goal, List), +StateIn, -StateOut)
|
|
1344
|
+
%% Apply Goal to each element (Goal/1)
|
|
1345
|
+
mi_call(maplist(Goal, List), StateIn, StateOut) :-
|
|
1346
|
+
mi_maplist1(Goal, List, StateIn, StateOut).
|
|
1347
|
+
|
|
1348
|
+
mi_maplist1(_, [], State, State).
|
|
1349
|
+
mi_maplist1(Goal, [H|T], StateIn, StateOut) :-
|
|
1350
|
+
copy_term(Goal, GoalCopy),
|
|
1351
|
+
GoalCopy =.. GoalList,
|
|
1352
|
+
append(GoalList, [H], GoalWithArg),
|
|
1353
|
+
CallGoal =.. GoalWithArg,
|
|
1354
|
+
mi_call(CallGoal, StateIn, State1),
|
|
1355
|
+
mi_maplist1(Goal, T, State1, StateOut).
|
|
1356
|
+
|
|
1357
|
+
%% mi_call(maplist(Goal, List1, List2), +StateIn, -StateOut)
|
|
1358
|
+
%% Apply Goal to pairs of elements (Goal/2)
|
|
1359
|
+
mi_call(maplist(Goal, List1, List2), StateIn, StateOut) :-
|
|
1360
|
+
mi_maplist2(Goal, List1, List2, StateIn, StateOut).
|
|
1361
|
+
|
|
1362
|
+
mi_maplist2(_, [], [], State, State).
|
|
1363
|
+
mi_maplist2(Goal, [H1|T1], [H2|T2], StateIn, StateOut) :-
|
|
1364
|
+
copy_term(Goal, GoalCopy),
|
|
1365
|
+
GoalCopy =.. GoalList,
|
|
1366
|
+
append(GoalList, [H1, H2], GoalWithArgs),
|
|
1367
|
+
CallGoal =.. GoalWithArgs,
|
|
1368
|
+
mi_call(CallGoal, StateIn, State1),
|
|
1369
|
+
mi_maplist2(Goal, T1, T2, State1, StateOut).
|
|
1370
|
+
|
|
1371
|
+
%% mi_call(maplist(Goal, L1, L2, L3), +StateIn, -StateOut)
|
|
1372
|
+
%% Apply Goal to triples of elements (Goal/3)
|
|
1373
|
+
mi_call(maplist(Goal, L1, L2, L3), StateIn, StateOut) :-
|
|
1374
|
+
mi_maplist3(Goal, L1, L2, L3, StateIn, StateOut).
|
|
1375
|
+
|
|
1376
|
+
mi_maplist3(_, [], [], [], State, State).
|
|
1377
|
+
mi_maplist3(Goal, [H1|T1], [H2|T2], [H3|T3], StateIn, StateOut) :-
|
|
1378
|
+
copy_term(Goal, GoalCopy),
|
|
1379
|
+
GoalCopy =.. GoalList,
|
|
1380
|
+
append(GoalList, [H1, H2, H3], GoalWithArgs),
|
|
1381
|
+
CallGoal =.. GoalWithArgs,
|
|
1382
|
+
mi_call(CallGoal, StateIn, State1),
|
|
1383
|
+
mi_maplist3(Goal, T1, T2, T3, State1, StateOut).
|
|
1384
|
+
|
|
1385
|
+
%% mi_call(foldl(Goal, List, V0, V), +StateIn, -StateOut)
|
|
1386
|
+
%% Left fold over list
|
|
1387
|
+
mi_call(foldl(Goal, List, V0, V), StateIn, StateOut) :-
|
|
1388
|
+
mi_foldl(Goal, List, V0, V, StateIn, StateOut).
|
|
1389
|
+
|
|
1390
|
+
mi_foldl(_, [], V, V, State, State).
|
|
1391
|
+
mi_foldl(Goal, [H|T], V0, V, StateIn, StateOut) :-
|
|
1392
|
+
copy_term(Goal, GoalCopy),
|
|
1393
|
+
GoalCopy =.. GoalList,
|
|
1394
|
+
append(GoalList, [H, V0, V1], GoalWithArgs),
|
|
1395
|
+
CallGoal =.. GoalWithArgs,
|
|
1396
|
+
mi_call(CallGoal, StateIn, State1),
|
|
1397
|
+
mi_foldl(Goal, T, V1, V, State1, StateOut).
|
|
1398
|
+
|
|
1399
|
+
%% ============================================================
|
|
1400
|
+
%% File I/O - All paths relative to /workspace
|
|
1401
|
+
%% ============================================================
|
|
1402
|
+
|
|
1403
|
+
%% Helper: resolve path relative to /workspace
|
|
1404
|
+
%% Prevents directory traversal outside workspace
|
|
1405
|
+
resolve_workspace_path(RelPath, FullPath) :-
|
|
1406
|
+
( atom(RelPath) -> atom_string(RelPath, RelPathStr)
|
|
1407
|
+
; RelPathStr = RelPath
|
|
1408
|
+
),
|
|
1409
|
+
% Remove leading slashes to make relative
|
|
1410
|
+
( sub_string(RelPathStr, 0, 1, _, "/")
|
|
1411
|
+
-> sub_string(RelPathStr, 1, _, 0, CleanPath)
|
|
1412
|
+
; CleanPath = RelPathStr
|
|
1413
|
+
),
|
|
1414
|
+
% Prevent directory traversal
|
|
1415
|
+
( sub_string(CleanPath, _, _, _, "..")
|
|
1416
|
+
-> throw(error(permission_error(access, directory, RelPath),
|
|
1417
|
+
context(resolve_workspace_path/2, 'Directory traversal not allowed')))
|
|
1418
|
+
; true
|
|
1419
|
+
),
|
|
1420
|
+
% Build full path
|
|
1421
|
+
atom_concat('/workspace/', CleanPath, FullPath).
|
|
1422
|
+
|
|
1423
|
+
%% mi_call(read_file_to_string(File, Content, Options), +StateIn, -StateOut)
|
|
1424
|
+
mi_call(read_file_to_string(File, Content, Options), StateIn, StateIn) :-
|
|
1425
|
+
!,
|
|
1426
|
+
resolve_workspace_path(File, FullPath),
|
|
1427
|
+
readutil:read_file_to_string(FullPath, Content, Options).
|
|
1428
|
+
|
|
1429
|
+
%% mi_call(read_file(File, Content), +StateIn, -StateOut)
|
|
1430
|
+
%% Simplified version without options
|
|
1431
|
+
mi_call(read_file(File, Content), StateIn, StateIn) :-
|
|
1432
|
+
!,
|
|
1433
|
+
resolve_workspace_path(File, FullPath),
|
|
1434
|
+
readutil:read_file_to_string(FullPath, Content, []).
|
|
1435
|
+
|
|
1436
|
+
%% mi_call(write_file(File, Content), +StateIn, -StateOut)
|
|
1437
|
+
%% Write string content to a file
|
|
1438
|
+
mi_call(write_file(File, Content), StateIn, StateIn) :-
|
|
1439
|
+
!,
|
|
1440
|
+
resolve_workspace_path(File, FullPath),
|
|
1441
|
+
open(FullPath, write, Stream),
|
|
1442
|
+
write(Stream, Content),
|
|
1443
|
+
close(Stream).
|
|
1444
|
+
|
|
1445
|
+
%% mi_call(append_file(File, Content), +StateIn, -StateOut)
|
|
1446
|
+
%% Append string content to a file
|
|
1447
|
+
mi_call(append_file(File, Content), StateIn, StateIn) :-
|
|
1448
|
+
!,
|
|
1449
|
+
resolve_workspace_path(File, FullPath),
|
|
1450
|
+
open(FullPath, append, Stream),
|
|
1451
|
+
write(Stream, Content),
|
|
1452
|
+
close(Stream).
|
|
1453
|
+
|
|
1454
|
+
%% mi_call(open(File, Mode, Stream), +StateIn, -StateOut)
|
|
1455
|
+
%% Open a file for reading/writing
|
|
1456
|
+
mi_call(open(File, Mode, Stream), StateIn, StateIn) :-
|
|
1457
|
+
!,
|
|
1458
|
+
resolve_workspace_path(File, FullPath),
|
|
1459
|
+
open(FullPath, Mode, Stream).
|
|
1460
|
+
|
|
1461
|
+
%% mi_call(close(Stream), +StateIn, -StateOut)
|
|
1462
|
+
mi_call(close(Stream), StateIn, StateIn) :-
|
|
1463
|
+
!,
|
|
1464
|
+
close(Stream).
|
|
1465
|
+
|
|
1466
|
+
%% mi_call(read_string(Stream, Length, Content), +StateIn, -StateOut)
|
|
1467
|
+
mi_call(read_string(Stream, Length, Content), StateIn, StateIn) :-
|
|
1468
|
+
!,
|
|
1469
|
+
read_string(Stream, Length, Content).
|
|
1470
|
+
|
|
1471
|
+
%% mi_call(write(Stream, Content), +StateIn, -StateOut)
|
|
1472
|
+
mi_call(write(Stream, Content), StateIn, StateIn) :-
|
|
1473
|
+
!,
|
|
1474
|
+
write(Stream, Content).
|
|
1475
|
+
|
|
1476
|
+
%% mi_call(exists_file(File), +StateIn, -StateOut)
|
|
1477
|
+
mi_call(exists_file(File), StateIn, StateIn) :-
|
|
1478
|
+
!,
|
|
1479
|
+
resolve_workspace_path(File, FullPath),
|
|
1480
|
+
exists_file(FullPath).
|
|
1481
|
+
|
|
1482
|
+
%% mi_call(exists_directory(Dir), +StateIn, -StateOut)
|
|
1483
|
+
mi_call(exists_directory(Dir), StateIn, StateIn) :-
|
|
1484
|
+
!,
|
|
1485
|
+
resolve_workspace_path(Dir, FullPath),
|
|
1486
|
+
exists_directory(FullPath).
|
|
1487
|
+
|
|
1488
|
+
%% mi_call(directory_files(Dir, Files), +StateIn, -StateOut)
|
|
1489
|
+
mi_call(directory_files(Dir, Files), StateIn, StateIn) :-
|
|
1490
|
+
!,
|
|
1491
|
+
resolve_workspace_path(Dir, FullPath),
|
|
1492
|
+
directory_files(FullPath, Files).
|
|
1493
|
+
|
|
1494
|
+
%% mi_call(make_directory(Dir), +StateIn, -StateOut)
|
|
1495
|
+
mi_call(make_directory(Dir), StateIn, StateIn) :-
|
|
1496
|
+
!,
|
|
1497
|
+
resolve_workspace_path(Dir, FullPath),
|
|
1498
|
+
make_directory(FullPath).
|
|
1499
|
+
|
|
1500
|
+
%% mi_call(delete_file(File), +StateIn, -StateOut)
|
|
1501
|
+
mi_call(delete_file(File), StateIn, StateIn) :-
|
|
1502
|
+
!,
|
|
1503
|
+
resolve_workspace_path(File, FullPath),
|
|
1504
|
+
delete_file(FullPath).
|
|
1505
|
+
|
|
1506
|
+
%% mi_call(delete_directory(Dir), +StateIn, -StateOut)
|
|
1507
|
+
mi_call(delete_directory(Dir), StateIn, StateIn) :-
|
|
1508
|
+
!,
|
|
1509
|
+
resolve_workspace_path(Dir, FullPath),
|
|
1510
|
+
delete_directory(FullPath).
|
|
1511
|
+
|
|
1512
|
+
%% ============================================================
|
|
1513
|
+
%% Catch/Throw - STATE THREADED
|
|
1514
|
+
%% ============================================================
|
|
1515
|
+
|
|
1516
|
+
%% mi_call(catch(Goal, Catcher, Recovery), +StateIn, -StateOut)
|
|
1517
|
+
mi_call(catch(Goal, Catcher, Recovery), StateIn, StateOut) :-
|
|
1518
|
+
catch(
|
|
1519
|
+
mi_call(Goal, StateIn, StateOut),
|
|
1520
|
+
Catcher,
|
|
1521
|
+
mi_call(Recovery, StateIn, StateOut)
|
|
1522
|
+
).
|
|
1523
|
+
|
|
1524
|
+
%% mi_call(throw(Error), +StateIn, -StateOut)
|
|
1525
|
+
mi_call(throw(Error), _StateIn, _StateOut) :-
|
|
1526
|
+
!,
|
|
1527
|
+
throw(Error).
|
|
1528
|
+
|
|
1529
|
+
%% ============================================================
|
|
1530
|
+
%% Module-Qualified Goals
|
|
1531
|
+
%% ============================================================
|
|
1532
|
+
|
|
1533
|
+
%% mi_call(Module:Goal, +StateIn, -StateOut)
|
|
1534
|
+
%% Module-qualified goals - handle special predicates and user-defined predicates
|
|
1535
|
+
|
|
1536
|
+
% First check if the unqualified Goal is a special MI predicate - if so, handle it directly
|
|
1537
|
+
mi_call(_Module:Goal, StateIn, StateOut) :-
|
|
1538
|
+
is_mi_special_predicate(Goal),
|
|
1539
|
+
!,
|
|
1540
|
+
mi_call(Goal, StateIn, StateOut).
|
|
1541
|
+
|
|
1542
|
+
% For regular module-qualified goals, use clause/2 for backtracking
|
|
1543
|
+
mi_call(Module:Goal, StateIn, StateOut) :-
|
|
1544
|
+
predicate_property(Module:Goal, defined),
|
|
1545
|
+
!, % CUT: If predicate is defined, don't fallback to call/1
|
|
1546
|
+
% Now try clause/2 for rules, allowing backtracking through clauses
|
|
1547
|
+
clause(Module:Goal, Body),
|
|
1548
|
+
( Body == true
|
|
1549
|
+
-> true % Fact - succeed with no body to interpret
|
|
1550
|
+
; mi_call(Body, StateIn, StateOut)
|
|
1551
|
+
).
|
|
1552
|
+
|
|
1553
|
+
mi_call(Module:Goal, StateIn, StateIn) :-
|
|
1554
|
+
% Fallback for built-ins only (predicate is NOT defined in module)
|
|
1555
|
+
call(Module:Goal).
|
|
1556
|
+
|
|
1557
|
+
%% ============================================================
|
|
1558
|
+
%% Built-in and User-Defined Predicates
|
|
1559
|
+
%% ============================================================
|
|
1560
|
+
|
|
1561
|
+
%% is_mi_special_predicate(+Goal)
|
|
1562
|
+
%% Check if Goal is a special predicate handled by dedicated mi_call clauses
|
|
1563
|
+
is_mi_special_predicate(answer(_)).
|
|
1564
|
+
is_mi_special_predicate(output(_)).
|
|
1565
|
+
is_mi_special_predicate(yield(_)).
|
|
1566
|
+
is_mi_special_predicate(log(_)).
|
|
1567
|
+
is_mi_special_predicate(system(_)).
|
|
1568
|
+
is_mi_special_predicate(user(_)). %% Add user message to context
|
|
1569
|
+
is_mi_special_predicate(task(_)).
|
|
1570
|
+
is_mi_special_predicate(task(_,_)).
|
|
1571
|
+
is_mi_special_predicate(task(_,_,_)).
|
|
1572
|
+
is_mi_special_predicate(task(_,_,_,_)).
|
|
1573
|
+
is_mi_special_predicate(task(_,_,_,_,_)).
|
|
1574
|
+
is_mi_special_predicate(task_named(_,_,_)). %% Transformed form of task/N
|
|
1575
|
+
is_mi_special_predicate(exec(_,_)).
|
|
1576
|
+
is_mi_special_predicate(param(_,_)).
|
|
1577
|
+
is_mi_special_predicate(param(_,_,_)).
|
|
1578
|
+
%% Prompt predicates (like task but with fresh memory)
|
|
1579
|
+
is_mi_special_predicate(prompt(_)).
|
|
1580
|
+
is_mi_special_predicate(prompt(_,_)).
|
|
1581
|
+
is_mi_special_predicate(prompt(_,_,_)).
|
|
1582
|
+
is_mi_special_predicate(prompt(_,_,_,_)).
|
|
1583
|
+
is_mi_special_predicate(prompt_named(_,_,_)). %% Transformed form of prompt/N
|
|
1584
|
+
%% Tool scoping predicates
|
|
1585
|
+
is_mi_special_predicate(with_tools(_,_)).
|
|
1586
|
+
is_mi_special_predicate(without_tools(_,_)).
|
|
1587
|
+
%% Context stack management
|
|
1588
|
+
is_mi_special_predicate(push_context).
|
|
1589
|
+
is_mi_special_predicate(push_context(_)).
|
|
1590
|
+
is_mi_special_predicate(pop_context).
|
|
1591
|
+
is_mi_special_predicate(clear_memory).
|
|
1592
|
+
%% Control flow
|
|
1593
|
+
is_mi_special_predicate((_,_)).
|
|
1594
|
+
is_mi_special_predicate((_;_)).
|
|
1595
|
+
is_mi_special_predicate((_->_)).
|
|
1596
|
+
is_mi_special_predicate((_->_;_)).
|
|
1597
|
+
is_mi_special_predicate(\+(_)).
|
|
1598
|
+
is_mi_special_predicate(catch(_,_,_)).
|
|
1599
|
+
is_mi_special_predicate(throw(_)).
|
|
1600
|
+
is_mi_special_predicate(!).
|
|
1601
|
+
is_mi_special_predicate(true).
|
|
1602
|
+
is_mi_special_predicate(fail).
|
|
1603
|
+
is_mi_special_predicate(false).
|
|
1604
|
+
is_mi_special_predicate(_:_).
|
|
1605
|
+
%% List predicates
|
|
1606
|
+
is_mi_special_predicate(member(_,_)).
|
|
1607
|
+
is_mi_special_predicate(append(_,_,_)).
|
|
1608
|
+
is_mi_special_predicate(length(_,_)).
|
|
1609
|
+
is_mi_special_predicate(nth0(_,_,_)).
|
|
1610
|
+
is_mi_special_predicate(nth1(_,_,_)).
|
|
1611
|
+
is_mi_special_predicate(last(_,_)).
|
|
1612
|
+
is_mi_special_predicate(reverse(_,_)).
|
|
1613
|
+
is_mi_special_predicate(sort(_,_)).
|
|
1614
|
+
is_mi_special_predicate(msort(_,_)).
|
|
1615
|
+
%% Meta-predicates
|
|
1616
|
+
is_mi_special_predicate(include(_,_,_)).
|
|
1617
|
+
is_mi_special_predicate(exclude(_,_,_)).
|
|
1618
|
+
is_mi_special_predicate(partition(_,_,_,_)).
|
|
1619
|
+
is_mi_special_predicate(forall(_,_)).
|
|
1620
|
+
is_mi_special_predicate(findall(_,_,_)).
|
|
1621
|
+
is_mi_special_predicate(bagof(_,_,_)).
|
|
1622
|
+
is_mi_special_predicate(setof(_,_,_)).
|
|
1623
|
+
is_mi_special_predicate(aggregate_all(_,_,_)).
|
|
1624
|
+
is_mi_special_predicate(maplist(_,_)).
|
|
1625
|
+
is_mi_special_predicate(maplist(_,_,_)).
|
|
1626
|
+
is_mi_special_predicate(maplist(_,_,_,_)).
|
|
1627
|
+
is_mi_special_predicate(foldl(_,_,_,_)).
|
|
1628
|
+
%% File I/O predicates
|
|
1629
|
+
is_mi_special_predicate(read_file_to_string(_,_,_)).
|
|
1630
|
+
is_mi_special_predicate(read_file(_,_)).
|
|
1631
|
+
is_mi_special_predicate(write_file(_,_)).
|
|
1632
|
+
is_mi_special_predicate(append_file(_,_)).
|
|
1633
|
+
is_mi_special_predicate(open(_,_,_)).
|
|
1634
|
+
is_mi_special_predicate(close(_)).
|
|
1635
|
+
is_mi_special_predicate(read_string(_,_,_)).
|
|
1636
|
+
is_mi_special_predicate(write(_,_)).
|
|
1637
|
+
is_mi_special_predicate(exists_file(_)).
|
|
1638
|
+
is_mi_special_predicate(exists_directory(_)).
|
|
1639
|
+
is_mi_special_predicate(directory_files(_,_)).
|
|
1640
|
+
is_mi_special_predicate(make_directory(_)).
|
|
1641
|
+
is_mi_special_predicate(delete_file(_)).
|
|
1642
|
+
is_mi_special_predicate(delete_directory(_)).
|
|
1643
|
+
|
|
1644
|
+
%% mi_call(Goal, +StateIn, -StateOut)
|
|
1645
|
+
%% Catch-all for built-in and user-defined predicates
|
|
1646
|
+
%% MUST NOT match special predicates that have dedicated handlers
|
|
1647
|
+
mi_call(Goal, StateIn, StateOut) :-
|
|
1648
|
+
( var(Goal)
|
|
1649
|
+
-> throw(error(instantiation_error, mi_call/3))
|
|
1650
|
+
; true
|
|
1651
|
+
),
|
|
1652
|
+
% Skip if this is a special predicate (has dedicated mi_call clause)
|
|
1653
|
+
\+ is_mi_special_predicate(Goal),
|
|
1654
|
+
mi_call_dispatch(Goal, StateIn, StateOut).
|
|
1655
|
+
|
|
1656
|
+
%% mi_call_dispatch(+Goal, +StateIn, -StateOut)
|
|
1657
|
+
%% Dispatch user-defined vs built-in predicates
|
|
1658
|
+
%% Uses separate clauses instead of if-then-else to preserve backtracking
|
|
1659
|
+
mi_call_dispatch(Goal, StateIn, StateIn) :-
|
|
1660
|
+
predicate_property(Goal, built_in),
|
|
1661
|
+
!, % Commit: it's built-in, no backtracking needed
|
|
1662
|
+
call(Goal).
|
|
1663
|
+
|
|
1664
|
+
mi_call_dispatch(Goal, StateIn, StateOut) :-
|
|
1665
|
+
get_session_id(SessionId),
|
|
1666
|
+
callable(Goal),
|
|
1667
|
+
predicate_property(SessionId:Goal, defined),
|
|
1668
|
+
% User-defined predicate - use clause/2 for backtracking
|
|
1669
|
+
% Add trace entry for call
|
|
1670
|
+
get_depth(StateIn, Depth),
|
|
1671
|
+
Goal =.. [Functor|Args],
|
|
1672
|
+
add_trace_entry(SessionId, call, Functor, Args, Depth),
|
|
1673
|
+
% Find a matching clause - allows backtracking to try multiple clauses
|
|
1674
|
+
clause(SessionId:Goal, Body),
|
|
1675
|
+
NewDepth is Depth + 1,
|
|
1676
|
+
set_depth(StateIn, NewDepth, State1),
|
|
1677
|
+
( mi_call(Body, State1, State2)
|
|
1678
|
+
-> add_trace_entry(SessionId, exit, Functor, Args, Depth),
|
|
1679
|
+
set_depth(State2, Depth, StateOut)
|
|
1680
|
+
; add_trace_entry(SessionId, fail, Functor, Args, Depth),
|
|
1681
|
+
fail
|
|
1682
|
+
).
|
|
1683
|
+
|
|
1684
|
+
mi_call_dispatch(Goal, StateIn, StateIn) :-
|
|
1685
|
+
% Fallback for external/library predicates
|
|
1686
|
+
get_session_id(SessionId),
|
|
1687
|
+
% Check if this predicate exists in the session module but not as user-defined
|
|
1688
|
+
\+ predicate_property(SessionId:Goal, defined),
|
|
1689
|
+
catch(call(SessionId:Goal), _, fail),
|
|
1690
|
+
!.
|
|
1691
|
+
|
|
1692
|
+
mi_call_dispatch(Goal, StateIn, StateIn) :-
|
|
1693
|
+
% Final fallback: call without module
|
|
1694
|
+
% Only for predicates not defined in session
|
|
1695
|
+
get_session_id(SessionId),
|
|
1696
|
+
\+ predicate_property(SessionId:Goal, defined),
|
|
1697
|
+
call(Goal).
|
|
1698
|
+
|
|
1699
|
+
%% ============================================================
|
|
1700
|
+
%% Helper Predicates
|
|
1701
|
+
%% ============================================================
|
|
1702
|
+
|
|
1703
|
+
%% interpolate_desc(+Template, +Params, -Result)
|
|
1704
|
+
interpolate_desc(Template, Params, Result) :-
|
|
1705
|
+
( is_dict(Params)
|
|
1706
|
+
-> dict_pairs(Params, _, Pairs),
|
|
1707
|
+
maplist([K-V, K=V]>>true, Pairs, Bindings),
|
|
1708
|
+
deepclause_strings:interpolate_string(Template, Bindings, Result)
|
|
1709
|
+
; Result = Template
|
|
1710
|
+
).
|
|
1711
|
+
|
|
1712
|
+
%% get_session_id(-SessionId)
|
|
1713
|
+
get_session_id(SessionId) :-
|
|
1714
|
+
nb_current(current_session_id, SessionId), !.
|
|
1715
|
+
get_session_id(default_session).
|
|
1716
|
+
|
|
1717
|
+
%% ============================================================
|
|
1718
|
+
%% Compile-Time String Interpolation Expansion
|
|
1719
|
+
%% ============================================================
|
|
1720
|
+
|
|
1721
|
+
%% expand_interpolations(+Term, +Bindings, -ExpandedTerm)
|
|
1722
|
+
%% Walk a term and expand strings containing {VarName} patterns
|
|
1723
|
+
%% Bindings is a list of 'VarName'=Variable pairs from read_term
|
|
1724
|
+
expand_interpolations(Term, Bindings, ExpandedTerm) :-
|
|
1725
|
+
expand_term_interp(Term, Bindings, ExpandedTerm).
|
|
1726
|
+
|
|
1727
|
+
%% expand_term_interp(+Term, +Bindings, -Expanded)
|
|
1728
|
+
%% Main term expansion predicate
|
|
1729
|
+
|
|
1730
|
+
% Variables pass through unchanged
|
|
1731
|
+
expand_term_interp(Var, _, Var) :-
|
|
1732
|
+
var(Var), !.
|
|
1733
|
+
|
|
1734
|
+
% Strings - check if they need interpolation
|
|
1735
|
+
expand_term_interp(String, Bindings, Expanded) :-
|
|
1736
|
+
string(String), !,
|
|
1737
|
+
( string_needs_interpolation(String)
|
|
1738
|
+
-> build_format_goal(String, Bindings, Expanded)
|
|
1739
|
+
; Expanded = String
|
|
1740
|
+
).
|
|
1741
|
+
|
|
1742
|
+
% Atoms - check if they need interpolation (for atom strings)
|
|
1743
|
+
expand_term_interp(Atom, Bindings, Expanded) :-
|
|
1744
|
+
atom(Atom),
|
|
1745
|
+
\+ Atom = [], % Not empty list
|
|
1746
|
+
atom_string(Atom, String),
|
|
1747
|
+
string_needs_interpolation(String), !,
|
|
1748
|
+
build_format_goal(String, Bindings, ExpandedGoal),
|
|
1749
|
+
% Wrap in atom conversion if original was atom
|
|
1750
|
+
Expanded = ExpandedGoal.
|
|
1751
|
+
|
|
1752
|
+
% Regular atoms pass through
|
|
1753
|
+
expand_term_interp(Atom, _, Atom) :-
|
|
1754
|
+
atom(Atom), !.
|
|
1755
|
+
|
|
1756
|
+
% Numbers pass through
|
|
1757
|
+
expand_term_interp(Num, _, Num) :-
|
|
1758
|
+
number(Num), !.
|
|
1759
|
+
|
|
1760
|
+
% Empty list
|
|
1761
|
+
expand_term_interp([], _, []) :- !.
|
|
1762
|
+
|
|
1763
|
+
% Lists - expand each element
|
|
1764
|
+
expand_term_interp([H|T], Bindings, [EH|ET]) :- !,
|
|
1765
|
+
expand_term_interp(H, Bindings, EH),
|
|
1766
|
+
expand_term_interp(T, Bindings, ET).
|
|
1767
|
+
|
|
1768
|
+
% Clause with body - special handling for string interpolation in goals
|
|
1769
|
+
expand_term_interp((Head :- Body), Bindings, (Head :- ExpandedBody)) :- !,
|
|
1770
|
+
expand_body_interp(Body, Bindings, ExpandedBody).
|
|
1771
|
+
|
|
1772
|
+
% Other compound terms - expand arguments
|
|
1773
|
+
expand_term_interp(Term, Bindings, ExpandedTerm) :-
|
|
1774
|
+
compound(Term), !,
|
|
1775
|
+
Term =.. [Functor|Args],
|
|
1776
|
+
maplist({Bindings}/[A, EA]>>expand_term_interp(A, Bindings, EA), Args, ExpandedArgs),
|
|
1777
|
+
ExpandedTerm =.. [Functor|ExpandedArgs].
|
|
1778
|
+
|
|
1779
|
+
%% expand_body_interp(+Body, +Bindings, -ExpandedBody)
|
|
1780
|
+
%% Expand goals in a clause body, handling control structures
|
|
1781
|
+
|
|
1782
|
+
% Variable goal
|
|
1783
|
+
expand_body_interp(Var, _, Var) :-
|
|
1784
|
+
var(Var), !.
|
|
1785
|
+
|
|
1786
|
+
% Conjunction
|
|
1787
|
+
expand_body_interp((A, B), Bindings, ExpandedConj) :- !,
|
|
1788
|
+
expand_body_interp(A, Bindings, EA),
|
|
1789
|
+
expand_body_interp(B, Bindings, EB),
|
|
1790
|
+
flatten_conjunction(EA, EB, ExpandedConj).
|
|
1791
|
+
|
|
1792
|
+
% Disjunction
|
|
1793
|
+
expand_body_interp((A ; B), Bindings, (EA ; EB)) :- !,
|
|
1794
|
+
expand_body_interp(A, Bindings, EA),
|
|
1795
|
+
expand_body_interp(B, Bindings, EB).
|
|
1796
|
+
|
|
1797
|
+
% If-then-else
|
|
1798
|
+
expand_body_interp((A -> B ; C), Bindings, (EA -> EB ; EC)) :- !,
|
|
1799
|
+
expand_body_interp(A, Bindings, EA),
|
|
1800
|
+
expand_body_interp(B, Bindings, EB),
|
|
1801
|
+
expand_body_interp(C, Bindings, EC).
|
|
1802
|
+
|
|
1803
|
+
% If-then
|
|
1804
|
+
expand_body_interp((A -> B), Bindings, (EA -> EB)) :- !,
|
|
1805
|
+
expand_body_interp(A, Bindings, EA),
|
|
1806
|
+
expand_body_interp(B, Bindings, EB).
|
|
1807
|
+
|
|
1808
|
+
% Soft cut
|
|
1809
|
+
expand_body_interp((A *-> B), Bindings, (EA *-> EB)) :- !,
|
|
1810
|
+
expand_body_interp(A, Bindings, EA),
|
|
1811
|
+
expand_body_interp(B, Bindings, EB).
|
|
1812
|
+
|
|
1813
|
+
% Negation
|
|
1814
|
+
expand_body_interp(\+(A), Bindings, \+(EA)) :- !,
|
|
1815
|
+
expand_body_interp(A, Bindings, EA).
|
|
1816
|
+
|
|
1817
|
+
% Goals that take string arguments - expand the string arg specially
|
|
1818
|
+
expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
1819
|
+
goal_with_string_arg(Goal, Functor, StringArg, RestArgs), !,
|
|
1820
|
+
( string_needs_interpolation(StringArg)
|
|
1821
|
+
-> build_format_call(StringArg, Bindings, TempVar, FormatGoal),
|
|
1822
|
+
rebuild_goal(Functor, TempVar, RestArgs, NewGoal),
|
|
1823
|
+
ExpandedGoal = (FormatGoal, NewGoal)
|
|
1824
|
+
; expand_rest_args(RestArgs, Bindings, ExpandedRestArgs),
|
|
1825
|
+
rebuild_goal(Functor, StringArg, ExpandedRestArgs, ExpandedGoal)
|
|
1826
|
+
).
|
|
1827
|
+
|
|
1828
|
+
% Other goals - expand arguments
|
|
1829
|
+
expand_body_interp(Goal, Bindings, ExpandedGoal) :-
|
|
1830
|
+
compound(Goal), !,
|
|
1831
|
+
Goal =.. [Functor|Args],
|
|
1832
|
+
maplist({Bindings}/[A, EA]>>expand_body_interp(A, Bindings, EA), Args, ExpandedArgs),
|
|
1833
|
+
ExpandedGoal =.. [Functor|ExpandedArgs].
|
|
1834
|
+
|
|
1835
|
+
% Atoms/other
|
|
1836
|
+
expand_body_interp(Goal, _, Goal).
|
|
1837
|
+
|
|
1838
|
+
%% goal_with_string_arg(+Goal, -Functor, -StringArg, -RestArgs)
|
|
1839
|
+
%% Match goals that take a string as their first argument
|
|
1840
|
+
goal_with_string_arg(answer(S), answer, S, []) :- string(S).
|
|
1841
|
+
goal_with_string_arg(answer(S), answer, S, []) :- atom(S), \+ S = [].
|
|
1842
|
+
goal_with_string_arg(system(S), system, S, []) :- string(S).
|
|
1843
|
+
goal_with_string_arg(system(S), system, S, []) :- atom(S), \+ S = [].
|
|
1844
|
+
goal_with_string_arg(user(S), user, S, []) :- string(S).
|
|
1845
|
+
goal_with_string_arg(user(S), user, S, []) :- atom(S), \+ S = [].
|
|
1846
|
+
goal_with_string_arg(output(S), output, S, []) :- string(S).
|
|
1847
|
+
goal_with_string_arg(output(S), output, S, []) :- atom(S), \+ S = [].
|
|
1848
|
+
goal_with_string_arg(log(S), log, S, []) :- string(S).
|
|
1849
|
+
goal_with_string_arg(log(S), log, S, []) :- atom(S), \+ S = [].
|
|
1850
|
+
goal_with_string_arg(yield(S), yield, S, []) :- string(S).
|
|
1851
|
+
goal_with_string_arg(yield(S), yield, S, []) :- atom(S), \+ S = [].
|
|
1852
|
+
goal_with_string_arg(task(S), task, S, []) :- string(S).
|
|
1853
|
+
goal_with_string_arg(task(S), task, S, []) :- atom(S), \+ S = [].
|
|
1854
|
+
goal_with_string_arg(task(S, V1), task, S, [V1]) :- string(S).
|
|
1855
|
+
goal_with_string_arg(task(S, V1), task, S, [V1]) :- atom(S), \+ S = [].
|
|
1856
|
+
goal_with_string_arg(task(S, V1, V2), task, S, [V1, V2]) :- string(S).
|
|
1857
|
+
goal_with_string_arg(task(S, V1, V2), task, S, [V1, V2]) :- atom(S), \+ S = [].
|
|
1858
|
+
goal_with_string_arg(task(S, V1, V2, V3), task, S, [V1, V2, V3]) :- string(S).
|
|
1859
|
+
goal_with_string_arg(task(S, V1, V2, V3), task, S, [V1, V2, V3]) :- atom(S), \+ S = [].
|
|
1860
|
+
%% task_named/3 - transformed form with embedded variable names
|
|
1861
|
+
goal_with_string_arg(task_named(S, Vars, Names), task_named, S, [Vars, Names]) :- string(S).
|
|
1862
|
+
goal_with_string_arg(task_named(S, Vars, Names), task_named, S, [Vars, Names]) :- atom(S), \+ S = [].
|
|
1863
|
+
goal_with_string_arg(prompt(S), prompt, S, []) :- string(S).
|
|
1864
|
+
goal_with_string_arg(prompt(S), prompt, S, []) :- atom(S), \+ S = [].
|
|
1865
|
+
goal_with_string_arg(prompt(S, V1), prompt, S, [V1]) :- string(S).
|
|
1866
|
+
goal_with_string_arg(prompt(S, V1), prompt, S, [V1]) :- atom(S), \+ S = [].
|
|
1867
|
+
goal_with_string_arg(prompt(S, V1, V2), prompt, S, [V1, V2]) :- string(S).
|
|
1868
|
+
goal_with_string_arg(prompt(S, V1, V2), prompt, S, [V1, V2]) :- atom(S), \+ S = [].
|
|
1869
|
+
goal_with_string_arg(prompt(S, V1, V2, V3), prompt, S, [V1, V2, V3]) :- string(S).
|
|
1870
|
+
goal_with_string_arg(prompt(S, V1, V2, V3), prompt, S, [V1, V2, V3]) :- atom(S), \+ S = [].
|
|
1871
|
+
%% prompt_named/3 - transformed form with embedded variable names
|
|
1872
|
+
goal_with_string_arg(prompt_named(S, Vars, Names), prompt_named, S, [Vars, Names]) :- string(S).
|
|
1873
|
+
goal_with_string_arg(prompt_named(S, Vars, Names), prompt_named, S, [Vars, Names]) :- atom(S), \+ S = [].
|
|
1874
|
+
|
|
1875
|
+
%% rebuild_goal(+Functor, +StringArg, +RestArgs, -Goal)
|
|
1876
|
+
%% Rebuild a goal with the string argument and rest args
|
|
1877
|
+
rebuild_goal(Functor, StringArg, [], Goal) :- !,
|
|
1878
|
+
Goal =.. [Functor, StringArg].
|
|
1879
|
+
rebuild_goal(Functor, StringArg, RestArgs, Goal) :-
|
|
1880
|
+
Goal =.. [Functor, StringArg | RestArgs].
|
|
1881
|
+
|
|
1882
|
+
%% expand_rest_args(+Args, +Bindings, -ExpandedArgs)
|
|
1883
|
+
expand_rest_args([], _, []).
|
|
1884
|
+
expand_rest_args([A|As], Bindings, [EA|EAs]) :-
|
|
1885
|
+
expand_body_interp(A, Bindings, EA),
|
|
1886
|
+
expand_rest_args(As, Bindings, EAs).
|
|
1887
|
+
|
|
1888
|
+
%% flatten_conjunction(+A, +B, -Conj)
|
|
1889
|
+
%% Flatten nested conjunctions properly
|
|
1890
|
+
flatten_conjunction((A1, A2), B, Result) :- !,
|
|
1891
|
+
flatten_conjunction(A2, B, Rest),
|
|
1892
|
+
Result = (A1, Rest).
|
|
1893
|
+
flatten_conjunction(A, B, (A, B)).
|
|
1894
|
+
|
|
1895
|
+
%% string_needs_interpolation(+String)
|
|
1896
|
+
%% Check if a string contains {VarName} patterns
|
|
1897
|
+
string_needs_interpolation(String) :-
|
|
1898
|
+
( string(String) -> S = String
|
|
1899
|
+
; atom(String) -> atom_string(String, S)
|
|
1900
|
+
; fail
|
|
1901
|
+
),
|
|
1902
|
+
sub_string(S, _, _, _, "{"),
|
|
1903
|
+
sub_string(S, _, _, _, "}").
|
|
1904
|
+
|
|
1905
|
+
%% build_format_call(+Template, +Bindings, -TempVar, -FormatGoal)
|
|
1906
|
+
%% Build a format/3 call that produces the interpolated string
|
|
1907
|
+
%% For variables in scope: use the variable directly
|
|
1908
|
+
%% For params (not in scope): generate param(name, Var) lookup
|
|
1909
|
+
build_format_call(Template, Bindings, TempVar, FullGoal) :-
|
|
1910
|
+
( string(Template) -> T = Template
|
|
1911
|
+
; atom_string(Template, T)
|
|
1912
|
+
),
|
|
1913
|
+
extract_interpolation_vars(T, VarNames, FormatString),
|
|
1914
|
+
lookup_vars_with_params(VarNames, Bindings, VarList, ParamGoals),
|
|
1915
|
+
FormatGoal = format(string(TempVar), FormatString, VarList),
|
|
1916
|
+
( ParamGoals == []
|
|
1917
|
+
-> FullGoal = FormatGoal
|
|
1918
|
+
; list_to_conjunction(ParamGoals, ParamConj),
|
|
1919
|
+
FullGoal = (ParamConj, FormatGoal)
|
|
1920
|
+
).
|
|
1921
|
+
|
|
1922
|
+
%% list_to_conjunction(+List, -Conjunction)
|
|
1923
|
+
list_to_conjunction([G], G) :- !.
|
|
1924
|
+
list_to_conjunction([G|Gs], (G, Rest)) :-
|
|
1925
|
+
list_to_conjunction(Gs, Rest).
|
|
1926
|
+
|
|
1927
|
+
%% build_format_goal(+String, +Bindings, -Goal)
|
|
1928
|
+
%% For direct string expansion (not in a goal context)
|
|
1929
|
+
build_format_goal(String, Bindings, Goal) :-
|
|
1930
|
+
build_format_call(String, Bindings, TempVar, FormatGoal),
|
|
1931
|
+
Goal = (FormatGoal, TempVar).
|
|
1932
|
+
|
|
1933
|
+
%% extract_interpolation_vars(+Template, -VarNames, -FormatString)
|
|
1934
|
+
%% Parse template to extract variable names and build format string
|
|
1935
|
+
extract_interpolation_vars(Template, VarNames, FormatString) :-
|
|
1936
|
+
string_codes(Template, Codes),
|
|
1937
|
+
extract_vars_from_codes(Codes, VarNames, FormatCodes),
|
|
1938
|
+
string_codes(FormatString, FormatCodes).
|
|
1939
|
+
|
|
1940
|
+
%% extract_vars_from_codes(+Codes, -VarNames, -FormatCodes)
|
|
1941
|
+
extract_vars_from_codes([], [], []) :- !.
|
|
1942
|
+
|
|
1943
|
+
% Found opening brace
|
|
1944
|
+
extract_vars_from_codes([0'{|Rest], [VarName|VarNames], [0'~, 0'w|FormatRest]) :- !,
|
|
1945
|
+
extract_var_until_close(Rest, VarNameCodes, AfterClose),
|
|
1946
|
+
atom_codes(VarName, VarNameCodes),
|
|
1947
|
+
extract_vars_from_codes(AfterClose, VarNames, FormatRest).
|
|
1948
|
+
|
|
1949
|
+
% Escape tilde for format/3
|
|
1950
|
+
extract_vars_from_codes([0'~|Rest], VarNames, [0'~, 0'~|FormatRest]) :- !,
|
|
1951
|
+
extract_vars_from_codes(Rest, VarNames, FormatRest).
|
|
1952
|
+
|
|
1953
|
+
% Regular character
|
|
1954
|
+
extract_vars_from_codes([C|Rest], VarNames, [C|FormatRest]) :-
|
|
1955
|
+
extract_vars_from_codes(Rest, VarNames, FormatRest).
|
|
1956
|
+
|
|
1957
|
+
%% extract_var_until_close(+Codes, -VarNameCodes, -Rest)
|
|
1958
|
+
extract_var_until_close([0'}|Rest], [], Rest) :- !.
|
|
1959
|
+
extract_var_until_close([C|Rest], [C|VarRest], Final) :-
|
|
1960
|
+
extract_var_until_close(Rest, VarRest, Final).
|
|
1961
|
+
extract_var_until_close([], [], []).
|
|
1962
|
+
|
|
1963
|
+
%% lookup_vars_with_params(+VarNames, +Bindings, -VarList, -ParamGoals)
|
|
1964
|
+
%% Look up each variable name:
|
|
1965
|
+
%% - If in Bindings (local Prolog variable) → use directly
|
|
1966
|
+
%% - If not in Bindings → generate param(name, Var) lookup
|
|
1967
|
+
lookup_vars_with_params([], _, [], []).
|
|
1968
|
+
lookup_vars_with_params([Name|Names], Bindings, [Var|Vars], ParamGoals) :-
|
|
1969
|
+
( member(Name=Var, Bindings)
|
|
1970
|
+
-> % Found as local variable
|
|
1971
|
+
ParamGoals = RestGoals
|
|
1972
|
+
; % Not a local variable - treat as param lookup
|
|
1973
|
+
% Convert to lowercase atom for param key
|
|
1974
|
+
downcase_atom(Name, LowerName),
|
|
1975
|
+
ParamGoals = [param(LowerName, Var)|RestGoals]
|
|
1976
|
+
),
|
|
1977
|
+
lookup_vars_with_params(Names, Bindings, Vars, RestGoals).
|
|
1978
|
+
|