@oh-my-pi/pi-coding-agent 16.1.13 → 16.1.14
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/CHANGELOG.md +27 -0
- package/dist/cli.js +5547 -2759
- package/dist/types/config/settings-schema.d.ts +44 -14
- package/dist/types/eval/__tests__/julia-prelude.test.d.ts +1 -0
- package/dist/types/eval/backend-helpers.d.ts +23 -0
- package/dist/types/eval/executor-base.d.ts +118 -0
- package/dist/types/eval/index.d.ts +2 -0
- package/dist/types/eval/jl/executor.d.ts +44 -0
- package/dist/types/eval/jl/index.d.ts +11 -0
- package/dist/types/eval/jl/kernel.d.ts +28 -0
- package/dist/types/eval/jl/prelude.d.ts +1 -0
- package/dist/types/eval/jl/runtime.d.ts +22 -0
- package/dist/types/eval/kernel-base.d.ts +105 -0
- package/dist/types/eval/py/kernel.d.ts +3 -61
- package/dist/types/eval/rb/executor.d.ts +77 -0
- package/dist/types/eval/rb/index.d.ts +11 -0
- package/dist/types/eval/rb/kernel.d.ts +31 -0
- package/dist/types/eval/rb/prelude.d.ts +1 -0
- package/dist/types/eval/rb/runtime.d.ts +23 -0
- package/dist/types/eval/runtime-env.d.ts +24 -0
- package/dist/types/eval/types.d.ts +3 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -15
- package/dist/types/extensibility/hooks/loader.d.ts +1 -25
- package/dist/types/extensibility/session-handler-types.d.ts +23 -0
- package/dist/types/jsonrpc/message-framing.d.ts +35 -0
- package/dist/types/mnemopi/embed-client.d.ts +7 -24
- package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +11 -0
- package/dist/types/modes/components/selector-helpers.d.ts +53 -0
- package/dist/types/modes/interactive-mode.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +8 -1
- package/dist/types/modes/types.d.ts +0 -2
- package/dist/types/modes/utils/copy-targets.d.ts +1 -1
- package/dist/types/modes/utils/interactive-context-helpers.d.ts +14 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +54 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +2 -2
- package/dist/types/stt/asr-client.d.ts +3 -29
- package/dist/types/subprocess/worker-client.d.ts +149 -0
- package/dist/types/subprocess/worker-runtime.d.ts +107 -0
- package/dist/types/tiny/title-client.d.ts +14 -34
- package/dist/types/tools/eval-backends.d.ts +6 -3
- package/dist/types/tools/eval-render.d.ts +6 -5
- package/dist/types/tools/eval.d.ts +13 -15
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tts/tts-client.d.ts +3 -28
- package/dist/types/tui/code-cell.d.ts +7 -0
- package/dist/types/utils/file-display-mode.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +6 -0
- package/package.json +12 -12
- package/src/config/settings-schema.ts +50 -18
- package/src/config/settings.ts +5 -0
- package/src/dap/client.ts +13 -107
- package/src/eval/__tests__/julia-prelude.test.ts +77 -0
- package/src/eval/backend-helpers.ts +48 -0
- package/src/eval/executor-base.ts +425 -0
- package/src/eval/index.ts +2 -0
- package/src/eval/jl/executor.ts +540 -0
- package/src/eval/jl/index.ts +54 -0
- package/src/eval/jl/kernel.ts +235 -0
- package/src/eval/jl/prelude.jl +930 -0
- package/src/eval/jl/prelude.ts +3 -0
- package/src/eval/jl/runner.jl +634 -0
- package/src/eval/jl/runtime.ts +118 -0
- package/src/eval/js/index.ts +3 -14
- package/src/eval/kernel-base.ts +569 -0
- package/src/eval/py/executor.ts +43 -252
- package/src/eval/py/index.ts +9 -20
- package/src/eval/py/kernel.ts +29 -544
- package/src/eval/rb/executor.ts +504 -0
- package/src/eval/rb/index.ts +54 -0
- package/src/eval/rb/kernel.ts +230 -0
- package/src/eval/rb/prelude.rb +721 -0
- package/src/eval/rb/prelude.ts +3 -0
- package/src/eval/rb/runner.rb +474 -0
- package/src/eval/rb/runtime.ts +132 -0
- package/src/eval/runtime-env.ts +104 -0
- package/src/eval/types.ts +3 -3
- package/src/extensibility/extensions/runner.ts +4 -11
- package/src/extensibility/hooks/loader.ts +3 -21
- package/src/extensibility/session-handler-types.ts +21 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/jsonrpc/message-framing.ts +142 -0
- package/src/lsp/client.ts +13 -109
- package/src/mnemopi/embed-client.ts +43 -198
- package/src/modes/components/agent-dashboard.ts +17 -40
- package/src/modes/components/chat-transcript-builder.ts +18 -102
- package/src/modes/components/custom-editor.ts +19 -0
- package/src/modes/components/extensions/extension-dashboard.ts +4 -13
- package/src/modes/components/extensions/extension-list.ts +16 -44
- package/src/modes/components/history-search.ts +4 -16
- package/src/modes/components/selector-helpers.ts +129 -0
- package/src/modes/components/settings-selector.ts +7 -5
- package/src/modes/components/tree-selector.ts +13 -18
- package/src/modes/controllers/event-controller.ts +3 -9
- package/src/modes/controllers/input-controller.ts +32 -54
- package/src/modes/interactive-mode.ts +5 -7
- package/src/modes/theme/theme.ts +35 -0
- package/src/modes/types.ts +0 -2
- package/src/modes/utils/copy-targets.ts +3 -2
- package/src/modes/utils/interactive-context-helpers.ts +27 -0
- package/src/modes/utils/transcript-render-helpers.ts +157 -0
- package/src/modes/utils/ui-helpers.ts +21 -126
- package/src/prompts/system/system-prompt.md +10 -10
- package/src/prompts/tools/bash.md +0 -1
- package/src/prompts/tools/eval.md +6 -4
- package/src/prompts/tools/find.md +0 -4
- package/src/prompts/tools/read.md +1 -2
- package/src/prompts/tools/replace.md +1 -1
- package/src/prompts/tools/search.md +0 -1
- package/src/sdk.ts +13 -7
- package/src/session/agent-session.ts +9 -7
- package/src/stt/asr-client.ts +35 -215
- package/src/stt/asr-worker.ts +29 -181
- package/src/subprocess/worker-client.ts +297 -0
- package/src/subprocess/worker-runtime.ts +277 -0
- package/src/task/executor.ts +4 -4
- package/src/tiny/title-client.ts +53 -219
- package/src/tiny/worker.ts +29 -180
- package/src/tools/eval-backends.ts +10 -3
- package/src/tools/eval-render.ts +17 -8
- package/src/tools/eval.ts +187 -22
- package/src/tools/index.ts +51 -28
- package/src/tts/tts-client.ts +38 -206
- package/src/tts/tts-worker.ts +23 -97
- package/src/tui/code-cell.ts +12 -1
- package/src/utils/file-display-mode.ts +2 -3
- package/src/web/parallel.ts +43 -42
- package/src/web/search/providers/parallel.ts +10 -99
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
# OMP Julia runner — subprocess wrapper used by the coding-agent host.
|
|
2
|
+
# Persistent Julia process that speaks NDJSON over stdout and a custom TSV protocol on stdin.
|
|
3
|
+
|
|
4
|
+
using Base64
|
|
5
|
+
|
|
6
|
+
const ORIGINAL_STDOUT = stdout
|
|
7
|
+
const ORIGINAL_STDERR = stderr
|
|
8
|
+
const ORIGINAL_STDIN = stdin
|
|
9
|
+
|
|
10
|
+
# Redirect stdin/stdout/stderr to prevent cell prints from corrupting NDJSON
|
|
11
|
+
out_rd, out_wr = redirect_stdout()
|
|
12
|
+
err_rd, err_wr = redirect_stderr()
|
|
13
|
+
redirect_stdin(devnull)
|
|
14
|
+
|
|
15
|
+
global current_rid = nothing
|
|
16
|
+
const write_lock = ReentrantLock()
|
|
17
|
+
const drain_state_lock = ReentrantLock()
|
|
18
|
+
|
|
19
|
+
mutable struct DrainBarrier
|
|
20
|
+
marker::Vector{UInt8}
|
|
21
|
+
remaining::Int
|
|
22
|
+
done::Channel{Nothing}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
const active_drain_barrier = Ref{Union{Nothing, DrainBarrier}}(nothing)
|
|
26
|
+
const drain_marker_counter = Ref{UInt}(0)
|
|
27
|
+
|
|
28
|
+
function json_parse(s::String)
|
|
29
|
+
chars = collect(s)
|
|
30
|
+
pos = 1
|
|
31
|
+
len = length(chars)
|
|
32
|
+
|
|
33
|
+
function skip_whitespace()
|
|
34
|
+
while pos <= len && isspace(chars[pos])
|
|
35
|
+
pos += 1
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
function parse_value()
|
|
40
|
+
skip_whitespace()
|
|
41
|
+
if pos > len
|
|
42
|
+
error("Unexpected EOF")
|
|
43
|
+
end
|
|
44
|
+
c = chars[pos]
|
|
45
|
+
if c == '"'
|
|
46
|
+
return parse_string()
|
|
47
|
+
elseif c == '{'
|
|
48
|
+
return parse_object()
|
|
49
|
+
elseif c == '['
|
|
50
|
+
return parse_array()
|
|
51
|
+
elseif (c == 't' || c == 'f')
|
|
52
|
+
return parse_boolean()
|
|
53
|
+
elseif c == 'n'
|
|
54
|
+
return parse_null()
|
|
55
|
+
elseif c == '-' || isdigit(c)
|
|
56
|
+
return parse_number()
|
|
57
|
+
else
|
|
58
|
+
error("Unexpected character at $pos: $c")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
function parse_string()
|
|
63
|
+
pos += 1 # skip '"'
|
|
64
|
+
res = IOBuffer()
|
|
65
|
+
while pos <= len
|
|
66
|
+
c = chars[pos]
|
|
67
|
+
if c == '"'
|
|
68
|
+
pos += 1 # skip '"'
|
|
69
|
+
return String(take!(res))
|
|
70
|
+
elseif c == '\\'
|
|
71
|
+
pos += 1
|
|
72
|
+
if pos > len; error("Unexpected EOF in string escape"); end
|
|
73
|
+
esc = chars[pos]
|
|
74
|
+
if esc == '"'
|
|
75
|
+
write(res, '"')
|
|
76
|
+
elseif esc == '\\'
|
|
77
|
+
write(res, '\\')
|
|
78
|
+
elseif esc == '/'
|
|
79
|
+
write(res, '/')
|
|
80
|
+
elseif esc == 'b'
|
|
81
|
+
write(res, '\b')
|
|
82
|
+
elseif esc == 'f'
|
|
83
|
+
write(res, '\f')
|
|
84
|
+
elseif esc == 'n'
|
|
85
|
+
write(res, '\n')
|
|
86
|
+
elseif esc == 'r'
|
|
87
|
+
write(res, '\r')
|
|
88
|
+
elseif esc == 't'
|
|
89
|
+
write(res, '\t')
|
|
90
|
+
elseif esc == 'u'
|
|
91
|
+
# parse 4 hex digits
|
|
92
|
+
hex = ""
|
|
93
|
+
for i in 1:4
|
|
94
|
+
pos += 1
|
|
95
|
+
hex *= chars[pos]
|
|
96
|
+
end
|
|
97
|
+
write(res, Char(parse(Int, hex, base=16)))
|
|
98
|
+
else
|
|
99
|
+
write(res, esc)
|
|
100
|
+
end
|
|
101
|
+
else
|
|
102
|
+
write(res, c)
|
|
103
|
+
end
|
|
104
|
+
pos += 1
|
|
105
|
+
end
|
|
106
|
+
error("Unterminated string")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
function parse_object()
|
|
110
|
+
pos += 1 # skip '{'
|
|
111
|
+
obj = Dict{String, Any}()
|
|
112
|
+
skip_whitespace()
|
|
113
|
+
if pos <= len && chars[pos] == '}'
|
|
114
|
+
pos += 1
|
|
115
|
+
return obj
|
|
116
|
+
end
|
|
117
|
+
while true
|
|
118
|
+
skip_whitespace()
|
|
119
|
+
if pos > len || chars[pos] != '"'
|
|
120
|
+
error("Expected string key in object at $pos")
|
|
121
|
+
end
|
|
122
|
+
key = parse_string()
|
|
123
|
+
skip_whitespace()
|
|
124
|
+
if pos > len || chars[pos] != ':'
|
|
125
|
+
error("Expected ':' at $pos")
|
|
126
|
+
end
|
|
127
|
+
pos += 1 # skip ':'
|
|
128
|
+
val = parse_value()
|
|
129
|
+
obj[key] = val
|
|
130
|
+
skip_whitespace()
|
|
131
|
+
if pos > len
|
|
132
|
+
error("Expected ',' or '}' in object at $pos")
|
|
133
|
+
end
|
|
134
|
+
c = chars[pos]
|
|
135
|
+
if c == '}'
|
|
136
|
+
pos += 1
|
|
137
|
+
return obj
|
|
138
|
+
elseif c == ','
|
|
139
|
+
pos += 1
|
|
140
|
+
else
|
|
141
|
+
error("Expected ',' or '}' in object at $pos, got '$c'")
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
function parse_array()
|
|
147
|
+
pos += 1 # skip '['
|
|
148
|
+
arr = Any[]
|
|
149
|
+
skip_whitespace()
|
|
150
|
+
if pos <= len && chars[pos] == ']'
|
|
151
|
+
pos += 1
|
|
152
|
+
return arr
|
|
153
|
+
end
|
|
154
|
+
while true
|
|
155
|
+
push!(arr, parse_value())
|
|
156
|
+
skip_whitespace()
|
|
157
|
+
if pos > len
|
|
158
|
+
error("Expected ',' or ']' in array")
|
|
159
|
+
end
|
|
160
|
+
c = chars[pos]
|
|
161
|
+
if c == ']'
|
|
162
|
+
pos += 1
|
|
163
|
+
return arr
|
|
164
|
+
elseif c == ','
|
|
165
|
+
pos += 1
|
|
166
|
+
else
|
|
167
|
+
error("Expected ',' or ']' in array at $pos, got '$c'")
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
function parse_boolean()
|
|
173
|
+
s_slice = String(chars[pos:min(len, pos+4)])
|
|
174
|
+
if startswith(s_slice, "true")
|
|
175
|
+
pos += 4
|
|
176
|
+
return true
|
|
177
|
+
elseif startswith(s_slice, "false")
|
|
178
|
+
pos += 5
|
|
179
|
+
return false
|
|
180
|
+
else
|
|
181
|
+
error("Expected boolean at $pos")
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
function parse_null()
|
|
186
|
+
s_slice = String(chars[pos:min(len, pos+3)])
|
|
187
|
+
if startswith(s_slice, "null")
|
|
188
|
+
pos += 4
|
|
189
|
+
return nothing
|
|
190
|
+
else
|
|
191
|
+
error("Expected null at $pos")
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
function parse_number()
|
|
196
|
+
start_pos = pos
|
|
197
|
+
while pos <= len
|
|
198
|
+
c = chars[pos]
|
|
199
|
+
if isdigit(c) || c in ['.', '-', '+', 'e', 'E']
|
|
200
|
+
pos += 1
|
|
201
|
+
else
|
|
202
|
+
break
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
num_str = String(chars[start_pos:pos-1])
|
|
206
|
+
val = tryparse(Int, num_str)
|
|
207
|
+
if val !== nothing
|
|
208
|
+
return val
|
|
209
|
+
end
|
|
210
|
+
val_f = tryparse(Float64, num_str)
|
|
211
|
+
if val_f !== nothing
|
|
212
|
+
return val_f
|
|
213
|
+
end
|
|
214
|
+
error("Invalid number format: $num_str")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
val = parse_value()
|
|
218
|
+
skip_whitespace()
|
|
219
|
+
if pos <= len
|
|
220
|
+
error("Extra data after JSON value at $pos")
|
|
221
|
+
end
|
|
222
|
+
return val
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
function json_serialize_string(s::AbstractString)
|
|
226
|
+
res = IOBuffer()
|
|
227
|
+
write(res, '"')
|
|
228
|
+
for c in s
|
|
229
|
+
if c == '"'
|
|
230
|
+
write(res, "\\\"")
|
|
231
|
+
elseif c == '\\'
|
|
232
|
+
write(res, "\\\\")
|
|
233
|
+
elseif c == '\n'
|
|
234
|
+
write(res, "\\n")
|
|
235
|
+
elseif c == '\r'
|
|
236
|
+
write(res, "\\r")
|
|
237
|
+
elseif c == '\t'
|
|
238
|
+
write(res, "\\t")
|
|
239
|
+
elseif c == '\f'
|
|
240
|
+
write(res, "\\f")
|
|
241
|
+
elseif c == '\b'
|
|
242
|
+
write(res, "\\b")
|
|
243
|
+
elseif UInt32(c) < 32
|
|
244
|
+
d1 = div(UInt32(c), 16)
|
|
245
|
+
d2 = rem(UInt32(c), 16)
|
|
246
|
+
hex_chars = "0123456789abcdef"
|
|
247
|
+
write(res, "\\u00" * hex_chars[d1 + 1] * hex_chars[d2 + 1])
|
|
248
|
+
else
|
|
249
|
+
write(res, c)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
write(res, '"')
|
|
253
|
+
return String(take!(res))
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
function json_serialize(v)
|
|
257
|
+
if v === nothing
|
|
258
|
+
return "null"
|
|
259
|
+
elseif v isa Bool
|
|
260
|
+
return v ? "true" : "false"
|
|
261
|
+
elseif v isa Number
|
|
262
|
+
return string(v)
|
|
263
|
+
elseif v isa AbstractString
|
|
264
|
+
return json_serialize_string(v)
|
|
265
|
+
elseif v isa Symbol
|
|
266
|
+
return json_serialize_string(string(v))
|
|
267
|
+
elseif v isa AbstractVector
|
|
268
|
+
return "[" * join([json_serialize(x) for x in v], ",") * "]"
|
|
269
|
+
elseif v isa AbstractDict
|
|
270
|
+
parts = String[]
|
|
271
|
+
for (k, val) in v
|
|
272
|
+
push!(parts, json_serialize_string(string(k)) * ":" * json_serialize(val))
|
|
273
|
+
end
|
|
274
|
+
return "{" * join(parts, ",") * "}"
|
|
275
|
+
else
|
|
276
|
+
return json_serialize_string(repr(v))
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
function emit_frame(frame)
|
|
281
|
+
lock(write_lock) do
|
|
282
|
+
println(ORIGINAL_STDOUT, json_serialize(frame))
|
|
283
|
+
flush(ORIGINAL_STDOUT)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
function find_subsequence(haystack::Vector{UInt8}, needle::Vector{UInt8})
|
|
288
|
+
needle_len = length(needle)
|
|
289
|
+
if needle_len == 0 || length(haystack) < needle_len
|
|
290
|
+
return nothing
|
|
291
|
+
end
|
|
292
|
+
last_start = length(haystack) - needle_len + 1
|
|
293
|
+
for start in 1:last_start
|
|
294
|
+
matched = true
|
|
295
|
+
@inbounds for offset in 1:needle_len
|
|
296
|
+
if haystack[start + offset - 1] != needle[offset]
|
|
297
|
+
matched = false
|
|
298
|
+
break
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
if matched
|
|
302
|
+
return start
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
return nothing
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
function marker_overlap(haystack::Vector{UInt8}, needle::Vector{UInt8})
|
|
309
|
+
max_overlap = min(length(haystack), length(needle) - 1)
|
|
310
|
+
for overlap in max_overlap:-1:1
|
|
311
|
+
matched = true
|
|
312
|
+
@inbounds for offset in 1:overlap
|
|
313
|
+
if haystack[length(haystack) - overlap + offset] != needle[offset]
|
|
314
|
+
matched = false
|
|
315
|
+
break
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
if matched
|
|
319
|
+
return overlap
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
return 0
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
function emit_stream_bytes(kind, bytes::Vector{UInt8})
|
|
326
|
+
rid = current_rid
|
|
327
|
+
if rid === nothing || isempty(bytes)
|
|
328
|
+
return
|
|
329
|
+
end
|
|
330
|
+
emit_frame(Dict("type" => kind, "id" => rid, "data" => String(copy(bytes))))
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
function signal_drain_barrier!(marker::Vector{UInt8})
|
|
334
|
+
done_channel = lock(drain_state_lock) do
|
|
335
|
+
barrier = active_drain_barrier[]
|
|
336
|
+
if barrier === nothing || barrier.marker != marker
|
|
337
|
+
return nothing
|
|
338
|
+
end
|
|
339
|
+
barrier.remaining -= 1
|
|
340
|
+
if barrier.remaining == 0
|
|
341
|
+
active_drain_barrier[] = nothing
|
|
342
|
+
return barrier.done
|
|
343
|
+
end
|
|
344
|
+
return nothing
|
|
345
|
+
end
|
|
346
|
+
if done_channel !== nothing
|
|
347
|
+
put!(done_channel, nothing)
|
|
348
|
+
end
|
|
349
|
+
return nothing
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
function process_stream_buffer!(buffer::Vector{UInt8}, kind; flush_all::Bool=false)
|
|
353
|
+
while true
|
|
354
|
+
barrier = lock(drain_state_lock) do
|
|
355
|
+
active_drain_barrier[]
|
|
356
|
+
end
|
|
357
|
+
marker = barrier === nothing ? nothing : barrier.marker
|
|
358
|
+
if marker === nothing
|
|
359
|
+
if !isempty(buffer)
|
|
360
|
+
emit_stream_bytes(kind, buffer)
|
|
361
|
+
empty!(buffer)
|
|
362
|
+
end
|
|
363
|
+
return
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
marker_index = find_subsequence(buffer, marker)
|
|
367
|
+
if marker_index !== nothing
|
|
368
|
+
emit_len = marker_index - 1
|
|
369
|
+
if emit_len > 0
|
|
370
|
+
emit_stream_bytes(kind, buffer[1:emit_len])
|
|
371
|
+
end
|
|
372
|
+
deleteat!(buffer, 1:(marker_index + length(marker) - 1))
|
|
373
|
+
signal_drain_barrier!(marker)
|
|
374
|
+
continue
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
keep_len = flush_all ? 0 : marker_overlap(buffer, marker)
|
|
378
|
+
emit_len = length(buffer) - keep_len
|
|
379
|
+
if emit_len > 0
|
|
380
|
+
emit_stream_bytes(kind, buffer[1:emit_len])
|
|
381
|
+
deleteat!(buffer, 1:emit_len)
|
|
382
|
+
end
|
|
383
|
+
return
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
function next_drain_marker()
|
|
388
|
+
drain_marker_counter[] += UInt(1)
|
|
389
|
+
return Vector{UInt8}(codeunits("\0__OMP_DRAIN__:" * string(drain_marker_counter[]) * ":" * string(time_ns()) * "\0"))
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
function await_stream_drains()
|
|
393
|
+
flush(stdout)
|
|
394
|
+
flush(stderr)
|
|
395
|
+
barrier = DrainBarrier(next_drain_marker(), 2, Channel{Nothing}(1))
|
|
396
|
+
lock(drain_state_lock) do
|
|
397
|
+
if active_drain_barrier[] !== nothing
|
|
398
|
+
error("Drain barrier already active")
|
|
399
|
+
end
|
|
400
|
+
active_drain_barrier[] = barrier
|
|
401
|
+
end
|
|
402
|
+
try
|
|
403
|
+
Base.write(out_wr, barrier.marker)
|
|
404
|
+
flush(out_wr)
|
|
405
|
+
Base.write(err_wr, barrier.marker)
|
|
406
|
+
flush(err_wr)
|
|
407
|
+
take!(barrier.done)
|
|
408
|
+
finally
|
|
409
|
+
lock(drain_state_lock) do
|
|
410
|
+
if active_drain_barrier[] === barrier
|
|
411
|
+
active_drain_barrier[] = nothing
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
return nothing
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
function drain_stream(rd, kind)
|
|
419
|
+
buffer = UInt8[]
|
|
420
|
+
try
|
|
421
|
+
while true
|
|
422
|
+
data = readavailable(rd)
|
|
423
|
+
if !isempty(data)
|
|
424
|
+
append!(buffer, data)
|
|
425
|
+
process_stream_buffer!(buffer, kind)
|
|
426
|
+
elseif eof(rd)
|
|
427
|
+
break
|
|
428
|
+
else
|
|
429
|
+
sleep(0.001)
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
catch
|
|
433
|
+
# ignore
|
|
434
|
+
finally
|
|
435
|
+
process_stream_buffer!(buffer, kind, flush_all=true)
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
@async drain_stream(out_rd, "stdout")
|
|
440
|
+
@async drain_stream(err_rd, "stderr")
|
|
441
|
+
|
|
442
|
+
function build_mime_bundle(value)
|
|
443
|
+
bundle = Dict{String, Any}()
|
|
444
|
+
|
|
445
|
+
# text/plain
|
|
446
|
+
io_plain = IOBuffer()
|
|
447
|
+
show(io_plain, MIME"text/plain"(), value)
|
|
448
|
+
bundle["text/plain"] = String(take!(io_plain))
|
|
449
|
+
|
|
450
|
+
# rich mime types
|
|
451
|
+
for mime_str in ["text/html", "text/markdown", "image/png", "image/jpeg"]
|
|
452
|
+
m = MIME(Symbol(mime_str))
|
|
453
|
+
if showable(m, value)
|
|
454
|
+
try
|
|
455
|
+
io = IOBuffer()
|
|
456
|
+
if mime_str in ["image/png", "image/jpeg"]
|
|
457
|
+
b64_io = Base64EncodePipe(io)
|
|
458
|
+
show(b64_io, m, value)
|
|
459
|
+
close(b64_io)
|
|
460
|
+
else
|
|
461
|
+
show(io, m, value)
|
|
462
|
+
end
|
|
463
|
+
bundle[mime_str] = String(take!(io))
|
|
464
|
+
catch
|
|
465
|
+
# ignore
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
if value isa AbstractDict || value isa AbstractVector
|
|
471
|
+
try
|
|
472
|
+
bundle["application/json"] = value
|
|
473
|
+
catch
|
|
474
|
+
# ignore
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
return bundle
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
struct OmpDisplay <: AbstractDisplay end
|
|
482
|
+
|
|
483
|
+
function Base.display(d::OmpDisplay, value)
|
|
484
|
+
rid = current_rid
|
|
485
|
+
if rid !== nothing
|
|
486
|
+
bundle = build_mime_bundle(value)
|
|
487
|
+
emit_frame(Dict("type" => "display", "id" => rid, "bundle" => bundle))
|
|
488
|
+
end
|
|
489
|
+
return nothing
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
pushdisplay(OmpDisplay())
|
|
493
|
+
|
|
494
|
+
function emit_error(rid, err, bt)
|
|
495
|
+
io = IOBuffer()
|
|
496
|
+
showerror(io, err)
|
|
497
|
+
err_str = String(take!(io))
|
|
498
|
+
|
|
499
|
+
tb = String[]
|
|
500
|
+
for frame in stacktrace(bt)
|
|
501
|
+
file = string(frame.file)
|
|
502
|
+
line = frame.line
|
|
503
|
+
func = string(frame.func)
|
|
504
|
+
push!(tb, " at $func ($file:$line)")
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
emit_frame(Dict(
|
|
508
|
+
"type" => "error",
|
|
509
|
+
"id" => rid,
|
|
510
|
+
"ename" => string(typeof(err)),
|
|
511
|
+
"evalue" => err_str,
|
|
512
|
+
"traceback" => tb
|
|
513
|
+
))
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
function should_display_result(parsed_expr)
|
|
517
|
+
if parsed_expr isa Expr && parsed_expr.head === :block
|
|
518
|
+
args = parsed_expr.args
|
|
519
|
+
if !isempty(args)
|
|
520
|
+
last_arg = args[end]
|
|
521
|
+
if last_arg isa Expr
|
|
522
|
+
if last_arg.head in [Symbol("="), :function, :struct, :using, :import, :const, :global, :local, :macro]
|
|
523
|
+
return false
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
return true
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
function apply_request_runtime(cwd, env_pairs)
|
|
532
|
+
try
|
|
533
|
+
if !isempty(cwd)
|
|
534
|
+
cd(cwd)
|
|
535
|
+
end
|
|
536
|
+
catch
|
|
537
|
+
# ignore
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
managed_env_keys = [
|
|
541
|
+
"PI_SESSION_FILE",
|
|
542
|
+
"PI_ARTIFACTS_DIR",
|
|
543
|
+
"PI_TOOL_BRIDGE_URL",
|
|
544
|
+
"PI_TOOL_BRIDGE_TOKEN",
|
|
545
|
+
"PI_TOOL_BRIDGE_SESSION",
|
|
546
|
+
"PI_EVAL_LOCAL_ROOTS"
|
|
547
|
+
]
|
|
548
|
+
for k in managed_env_keys
|
|
549
|
+
delete!(ENV, k)
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
if !isempty(env_pairs)
|
|
553
|
+
for pair in split(env_pairs, ' ')
|
|
554
|
+
if !isempty(pair)
|
|
555
|
+
try
|
|
556
|
+
k_b64, v_b64 = split(pair, ':', limit=2)
|
|
557
|
+
k = String(base64decode(string(k_b64)))
|
|
558
|
+
v = String(base64decode(string(v_b64)))
|
|
559
|
+
ENV[k] = v
|
|
560
|
+
catch
|
|
561
|
+
# ignore
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# Main loop
|
|
569
|
+
function main()
|
|
570
|
+
while !eof(ORIGINAL_STDIN)
|
|
571
|
+
line = readline(ORIGINAL_STDIN)
|
|
572
|
+
if isempty(line)
|
|
573
|
+
continue
|
|
574
|
+
end
|
|
575
|
+
parts = split(line, '\t')
|
|
576
|
+
cmd = string(parts[1])
|
|
577
|
+
if cmd == "exit"
|
|
578
|
+
break
|
|
579
|
+
elseif cmd == "run"
|
|
580
|
+
if length(parts) < 7
|
|
581
|
+
continue
|
|
582
|
+
end
|
|
583
|
+
rid = string(parts[2])
|
|
584
|
+
cwd = String(base64decode(string(parts[3])))
|
|
585
|
+
silent = string(parts[4]) == "1"
|
|
586
|
+
store_history = string(parts[5]) == "1"
|
|
587
|
+
env_pairs = string(parts[6])
|
|
588
|
+
code = String(base64decode(string(parts[7])))
|
|
589
|
+
|
|
590
|
+
global current_rid = rid
|
|
591
|
+
emit_frame(Dict("type" => "started", "id" => rid))
|
|
592
|
+
|
|
593
|
+
apply_request_runtime(cwd, env_pairs)
|
|
594
|
+
|
|
595
|
+
exec_status = "ok"
|
|
596
|
+
try
|
|
597
|
+
parsed = Meta.parse("begin\n" * code * "\nend")
|
|
598
|
+
if parsed isa Expr && parsed.head === :error
|
|
599
|
+
# Syntax error from parser
|
|
600
|
+
exec_status = "error"
|
|
601
|
+
emit_frame(Dict(
|
|
602
|
+
"type" => "error",
|
|
603
|
+
"id" => rid,
|
|
604
|
+
"ename" => "ParseError",
|
|
605
|
+
"evalue" => string(parsed.args[1]),
|
|
606
|
+
"traceback" => String[]
|
|
607
|
+
))
|
|
608
|
+
else
|
|
609
|
+
ans = Core.eval(Main, parsed)
|
|
610
|
+
if ans !== nothing && !silent && should_display_result(parsed)
|
|
611
|
+
bundle = build_mime_bundle(ans)
|
|
612
|
+
emit_frame(Dict("type" => "result", "id" => rid, "bundle" => bundle))
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
catch err
|
|
616
|
+
exec_status = "error"
|
|
617
|
+
emit_error(rid, err, catch_backtrace())
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
await_stream_drains()
|
|
621
|
+
|
|
622
|
+
emit_frame(Dict(
|
|
623
|
+
"type" => "done",
|
|
624
|
+
"id" => rid,
|
|
625
|
+
"status" => exec_status,
|
|
626
|
+
"executionCount" => 1,
|
|
627
|
+
"cancelled" => false
|
|
628
|
+
))
|
|
629
|
+
global current_rid = nothing
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
main()
|