@aexol/spectral 0.7.1 → 0.7.5
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 +5 -0
- package/dist/agent/agents.js +1 -1
- package/dist/agent/index.js +199 -184
- package/dist/commands/serve.js +0 -3
- package/dist/designer/data/systems/renault/DESIGN.md +1 -1
- package/dist/designer/philosophies.js +668 -0
- package/dist/mcp/sampling-handler.js +1 -1
- package/dist/memory/commands/status.js +1 -1
- package/dist/memory/compaction.js +2 -2
- package/dist/memory/config.js +1 -1
- package/dist/memory/debug-log.js +1 -1
- package/dist/memory/hooks/compaction-hook.js +29 -0
- package/dist/memory/index.js +2 -0
- package/dist/memory/observer.js +2 -2
- package/dist/memory/project-observations-store.js +14 -0
- package/dist/memory/tokens.js +1 -1
- package/dist/memory/tools/read-project-observations.js +82 -0
- package/dist/memory/tools/recall-observation.js +2 -2
- package/dist/pi/agent-core/agent-loop.js +501 -0
- package/dist/pi/agent-core/agent.js +401 -0
- package/dist/pi/agent-core/harness/agent-harness.js +899 -0
- package/dist/pi/agent-core/harness/compaction/branch-summarization.js +173 -0
- package/dist/pi/agent-core/harness/compaction/compaction.js +532 -0
- package/dist/pi/agent-core/harness/compaction/utils.js +130 -0
- package/dist/pi/agent-core/harness/env/nodejs.js +485 -0
- package/dist/pi/agent-core/harness/messages.js +101 -0
- package/dist/pi/agent-core/harness/prompt-templates.js +229 -0
- package/dist/pi/agent-core/harness/session/jsonl-repo.js +100 -0
- package/dist/pi/agent-core/harness/session/jsonl-storage.js +230 -0
- package/dist/pi/agent-core/harness/session/memory-repo.js +41 -0
- package/dist/pi/agent-core/harness/session/memory-storage.js +113 -0
- package/dist/pi/agent-core/harness/session/repo-utils.js +38 -0
- package/dist/pi/agent-core/harness/session/session.js +196 -0
- package/dist/pi/agent-core/harness/session/uuid.js +49 -0
- package/dist/pi/agent-core/harness/skills.js +310 -0
- package/dist/pi/agent-core/harness/system-prompt.js +29 -0
- package/dist/pi/agent-core/harness/types.js +93 -0
- package/dist/pi/agent-core/harness/utils/shell-output.js +125 -0
- package/dist/pi/agent-core/harness/utils/truncate.js +289 -0
- package/dist/pi/agent-core/index.js +24 -0
- package/dist/pi/agent-core/node.js +2 -0
- package/dist/pi/agent-core/proxy.js +277 -0
- package/dist/pi/agent-core/types.js +1 -0
- package/dist/pi/ai/api-registry.js +43 -0
- package/dist/pi/ai/cli.js +120 -0
- package/dist/pi/ai/env-api-keys.js +169 -0
- package/dist/pi/ai/image-models.generated.js +441 -0
- package/dist/pi/ai/image-models.js +22 -0
- package/dist/pi/ai/images-api-registry.js +21 -0
- package/dist/pi/ai/images.js +13 -0
- package/dist/pi/ai/index.js +18 -0
- package/dist/pi/ai/models.generated.js +16220 -0
- package/dist/pi/ai/models.js +70 -0
- package/dist/pi/ai/oauth.js +1 -0
- package/dist/pi/ai/providers/anthropic.js +945 -0
- package/dist/pi/ai/providers/faux.js +367 -0
- package/dist/pi/ai/providers/github-copilot-headers.js +28 -0
- package/dist/pi/ai/providers/openai-completions.js +945 -0
- package/dist/pi/ai/providers/openai-prompt-cache.js +9 -0
- package/dist/pi/ai/providers/register-builtins.js +97 -0
- package/dist/pi/ai/providers/simple-options.js +40 -0
- package/dist/pi/ai/providers/transform-messages.js +183 -0
- package/dist/pi/ai/session-resources.js +21 -0
- package/dist/pi/ai/stream.js +26 -0
- package/dist/pi/ai/types.js +1 -0
- package/dist/pi/ai/utils/diagnostics.js +24 -0
- package/dist/pi/ai/utils/event-stream.js +80 -0
- package/dist/pi/ai/utils/hash.js +13 -0
- package/dist/pi/ai/utils/headers.js +7 -0
- package/dist/pi/ai/utils/json-parse.js +112 -0
- package/dist/pi/ai/utils/node-http-proxy.js +96 -0
- package/dist/pi/ai/utils/oauth/anthropic.js +334 -0
- package/dist/pi/ai/utils/oauth/device-code.js +54 -0
- package/dist/pi/ai/utils/oauth/github-copilot.js +270 -0
- package/dist/pi/ai/utils/oauth/index.js +121 -0
- package/dist/pi/ai/utils/oauth/oauth-page.js +104 -0
- package/dist/pi/ai/utils/oauth/openai-codex.js +384 -0
- package/dist/pi/ai/utils/oauth/pkce.js +30 -0
- package/dist/pi/ai/utils/oauth/types.js +1 -0
- package/dist/pi/ai/utils/overflow.js +150 -0
- package/dist/pi/ai/utils/sanitize-unicode.js +25 -0
- package/dist/pi/ai/utils/typebox-helpers.js +20 -0
- package/dist/pi/ai/utils/validation.js +280 -0
- package/dist/pi/coding-agent/bun/cli.js +7 -0
- package/dist/pi/coding-agent/bun/restore-sandbox-env.js +31 -0
- package/dist/pi/coding-agent/cli/args.js +340 -0
- package/dist/pi/coding-agent/cli/file-processor.js +82 -0
- package/dist/pi/coding-agent/cli/initial-message.js +21 -0
- package/dist/pi/coding-agent/cli.js +17 -0
- package/dist/pi/coding-agent/config.js +414 -0
- package/dist/pi/coding-agent/core/agent-session-runtime.js +299 -0
- package/dist/pi/coding-agent/core/agent-session-services.js +117 -0
- package/dist/pi/coding-agent/core/agent-session.js +2498 -0
- package/dist/pi/coding-agent/core/auth-guidance.js +20 -0
- package/dist/pi/coding-agent/core/auth-storage.js +441 -0
- package/dist/pi/coding-agent/core/bash-executor.js +110 -0
- package/dist/pi/coding-agent/core/compaction/branch-summarization.js +242 -0
- package/dist/pi/coding-agent/core/compaction/compaction.js +624 -0
- package/dist/pi/coding-agent/core/compaction/index.js +6 -0
- package/dist/pi/coding-agent/core/compaction/utils.js +152 -0
- package/dist/pi/coding-agent/core/defaults.js +1 -0
- package/dist/pi/coding-agent/core/diagnostics.js +1 -0
- package/dist/pi/coding-agent/core/event-bus.js +24 -0
- package/dist/pi/coding-agent/core/exec.js +74 -0
- package/dist/pi/coding-agent/core/export-html/ansi-to-html.js +248 -0
- package/dist/pi/coding-agent/core/export-html/index.js +225 -0
- package/dist/pi/coding-agent/core/export-html/tool-renderer.js +107 -0
- package/dist/pi/coding-agent/core/extensions/index.js +8 -0
- package/dist/pi/coding-agent/core/extensions/loader.js +485 -0
- package/dist/pi/coding-agent/core/extensions/runner.js +824 -0
- package/dist/pi/coding-agent/core/extensions/types.js +44 -0
- package/dist/pi/coding-agent/core/extensions/wrapper.js +21 -0
- package/dist/pi/coding-agent/core/footer-data-provider.js +309 -0
- package/dist/pi/coding-agent/core/http-dispatcher.js +47 -0
- package/dist/pi/coding-agent/core/index.js +11 -0
- package/dist/pi/coding-agent/core/keybindings.js +294 -0
- package/dist/pi/coding-agent/core/messages.js +122 -0
- package/dist/pi/coding-agent/core/model-registry.js +728 -0
- package/dist/pi/coding-agent/core/model-resolver.js +494 -0
- package/dist/pi/coding-agent/core/output-guard.js +58 -0
- package/dist/pi/coding-agent/core/package-manager.js +2020 -0
- package/dist/pi/coding-agent/core/prompt-templates.js +237 -0
- package/dist/pi/coding-agent/core/provider-display-names.js +32 -0
- package/dist/pi/coding-agent/core/resolve-config-value.js +125 -0
- package/dist/pi/coding-agent/core/resource-loader.js +733 -0
- package/dist/pi/coding-agent/core/sdk.js +282 -0
- package/dist/pi/coding-agent/core/session-cwd.js +37 -0
- package/dist/pi/coding-agent/core/session-manager.js +1146 -0
- package/dist/pi/coding-agent/core/settings-manager.js +794 -0
- package/dist/pi/coding-agent/core/skills.js +386 -0
- package/dist/pi/coding-agent/core/slash-commands.js +24 -0
- package/dist/pi/coding-agent/core/source-info.js +18 -0
- package/dist/pi/coding-agent/core/system-prompt.js +122 -0
- package/dist/pi/coding-agent/core/telemetry.js +8 -0
- package/dist/pi/coding-agent/core/timings.js +30 -0
- package/dist/pi/coding-agent/core/tools/bash.js +341 -0
- package/dist/pi/coding-agent/core/tools/edit-diff.js +344 -0
- package/dist/pi/coding-agent/core/tools/edit.js +324 -0
- package/dist/pi/coding-agent/core/tools/file-mutation-queue.js +36 -0
- package/dist/pi/coding-agent/core/tools/find.js +297 -0
- package/dist/pi/coding-agent/core/tools/grep.js +303 -0
- package/dist/pi/coding-agent/core/tools/index.js +111 -0
- package/dist/pi/coding-agent/core/tools/ls.js +168 -0
- package/dist/pi/coding-agent/core/tools/output-accumulator.js +183 -0
- package/dist/pi/coding-agent/core/tools/path-utils.js +61 -0
- package/dist/pi/coding-agent/core/tools/read.js +288 -0
- package/dist/pi/coding-agent/core/tools/render-utils.js +48 -0
- package/dist/pi/coding-agent/core/tools/tool-definition-wrapper.js +33 -0
- package/dist/pi/coding-agent/core/tools/truncate.js +214 -0
- package/dist/pi/coding-agent/core/tools/write.js +212 -0
- package/dist/pi/coding-agent/index.js +41 -0
- package/dist/pi/coding-agent/main.js +5 -0
- package/dist/pi/coding-agent/migrations.js +280 -0
- package/dist/pi/coding-agent/modes/index.js +7 -0
- package/dist/pi/coding-agent/modes/interactive/components/diff.js +132 -0
- package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +35 -0
- package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +32 -0
- package/dist/pi/coding-agent/modes/interactive/interactive-mode.js +3 -0
- package/dist/pi/coding-agent/modes/interactive/theme/theme.js +1023 -0
- package/dist/pi/coding-agent/modes/print-mode.js +130 -0
- package/dist/pi/coding-agent/modes/rpc/jsonl.js +48 -0
- package/dist/pi/coding-agent/modes/rpc/rpc-client.js +409 -0
- package/dist/pi/coding-agent/modes/rpc/rpc-mode.js +600 -0
- package/dist/pi/coding-agent/modes/rpc/rpc-types.js +7 -0
- package/dist/pi/coding-agent/utils/ansi.js +51 -0
- package/dist/pi/coding-agent/utils/changelog.js +86 -0
- package/dist/pi/coding-agent/utils/child-process.js +87 -0
- package/dist/pi/coding-agent/utils/clipboard-image.js +244 -0
- package/dist/pi/coding-agent/utils/clipboard-native.js +13 -0
- package/dist/pi/coding-agent/utils/clipboard.js +116 -0
- package/dist/pi/coding-agent/utils/exif-orientation.js +157 -0
- package/dist/pi/coding-agent/utils/frontmatter.js +25 -0
- package/dist/pi/coding-agent/utils/fs-watch.js +24 -0
- package/dist/pi/coding-agent/utils/git.js +162 -0
- package/dist/pi/coding-agent/utils/html.js +39 -0
- package/dist/pi/coding-agent/utils/image-convert.js +38 -0
- package/dist/pi/coding-agent/utils/image-resize.js +136 -0
- package/dist/pi/coding-agent/utils/mime.js +68 -0
- package/dist/pi/coding-agent/utils/paths.js +91 -0
- package/dist/pi/coding-agent/utils/photon.js +120 -0
- package/dist/pi/coding-agent/utils/pi-user-agent.js +4 -0
- package/dist/pi/coding-agent/utils/shell.js +194 -0
- package/dist/pi/coding-agent/utils/sleep.js +16 -0
- package/dist/pi/coding-agent/utils/syntax-highlight.js +117 -0
- package/dist/pi/coding-agent/utils/tools-manager.js +327 -0
- package/dist/pi/coding-agent/utils/version-check.js +81 -0
- package/dist/pi/coding-agent/utils/windows-self-update.js +76 -0
- package/dist/pi/tui/autocomplete.js +631 -0
- package/dist/pi/tui/components/box.js +103 -0
- package/dist/pi/tui/components/cancellable-loader.js +34 -0
- package/dist/pi/tui/components/editor.js +1915 -0
- package/dist/pi/tui/components/image.js +88 -0
- package/dist/pi/tui/components/input.js +425 -0
- package/dist/pi/tui/components/loader.js +68 -0
- package/dist/pi/tui/components/markdown.js +633 -0
- package/dist/pi/tui/components/select-list.js +158 -0
- package/dist/pi/tui/components/settings-list.js +184 -0
- package/dist/pi/tui/components/spacer.js +22 -0
- package/dist/pi/tui/components/text.js +88 -0
- package/dist/pi/tui/components/truncated-text.js +50 -0
- package/dist/pi/tui/editor-component.js +1 -0
- package/dist/pi/tui/fuzzy.js +109 -0
- package/dist/pi/tui/index.js +31 -0
- package/dist/pi/tui/keybindings.js +173 -0
- package/dist/pi/tui/keys.js +1172 -0
- package/dist/pi/tui/kill-ring.js +43 -0
- package/dist/pi/tui/stdin-buffer.js +360 -0
- package/dist/pi/tui/terminal-image.js +335 -0
- package/dist/pi/tui/terminal.js +324 -0
- package/dist/pi/tui/tui.js +1076 -0
- package/dist/pi/tui/undo-stack.js +24 -0
- package/dist/pi/tui/utils.js +1016 -0
- package/dist/relay/dispatcher.js +30 -0
- package/dist/server/handlers/queue.js +52 -0
- package/dist/server/pi-bridge.js +9 -1
- package/dist/server/session-stream.js +76 -111
- package/dist/server/storage.js +154 -2
- package/dist/server/title-generator.js +14 -153
- package/package.json +24 -6
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC mode: Headless operation with JSON stdin/stdout protocol.
|
|
3
|
+
*
|
|
4
|
+
* Used for embedding the agent in other applications.
|
|
5
|
+
* Receives commands as JSON on stdin, outputs events and responses as JSON on stdout.
|
|
6
|
+
*
|
|
7
|
+
* Protocol:
|
|
8
|
+
* - Commands: JSON objects with `type` field, optional `id` for correlation
|
|
9
|
+
* - Responses: JSON objects with `type: "response"`, `command`, `success`, and optional `data`/`error`
|
|
10
|
+
* - Events: AgentSessionEvent objects streamed as they occur
|
|
11
|
+
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
|
+
*/
|
|
13
|
+
import * as crypto from "node:crypto";
|
|
14
|
+
import { takeOverStdout, writeRawStdout } from "../../core/output-guard.js";
|
|
15
|
+
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
16
|
+
import { theme } from "../interactive/theme/theme.js";
|
|
17
|
+
import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js";
|
|
18
|
+
/**
|
|
19
|
+
* Run in RPC mode.
|
|
20
|
+
* Listens for JSON commands on stdin, outputs events and responses on stdout.
|
|
21
|
+
*/
|
|
22
|
+
export async function runRpcMode(runtimeHost) {
|
|
23
|
+
takeOverStdout();
|
|
24
|
+
let session = runtimeHost.session;
|
|
25
|
+
let unsubscribe;
|
|
26
|
+
const output = (obj) => {
|
|
27
|
+
writeRawStdout(serializeJsonLine(obj));
|
|
28
|
+
};
|
|
29
|
+
const success = (id, command, data) => {
|
|
30
|
+
if (data === undefined) {
|
|
31
|
+
return { id, type: "response", command, success: true };
|
|
32
|
+
}
|
|
33
|
+
return { id, type: "response", command, success: true, data };
|
|
34
|
+
};
|
|
35
|
+
const error = (id, command, message) => {
|
|
36
|
+
return { id, type: "response", command, success: false, error: message };
|
|
37
|
+
};
|
|
38
|
+
// Pending extension UI requests waiting for response
|
|
39
|
+
const pendingExtensionRequests = new Map();
|
|
40
|
+
// Shutdown request flag
|
|
41
|
+
let shutdownRequested = false;
|
|
42
|
+
let shuttingDown = false;
|
|
43
|
+
const signalCleanupHandlers = [];
|
|
44
|
+
/** Helper for dialog methods with signal/timeout support */
|
|
45
|
+
function createDialogPromise(opts, defaultValue, request, parseResponse) {
|
|
46
|
+
if (opts?.signal?.aborted)
|
|
47
|
+
return Promise.resolve(defaultValue);
|
|
48
|
+
const id = crypto.randomUUID();
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
let timeoutId;
|
|
51
|
+
const cleanup = () => {
|
|
52
|
+
if (timeoutId)
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
55
|
+
pendingExtensionRequests.delete(id);
|
|
56
|
+
};
|
|
57
|
+
const onAbort = () => {
|
|
58
|
+
cleanup();
|
|
59
|
+
resolve(defaultValue);
|
|
60
|
+
};
|
|
61
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
62
|
+
if (opts?.timeout) {
|
|
63
|
+
timeoutId = setTimeout(() => {
|
|
64
|
+
cleanup();
|
|
65
|
+
resolve(defaultValue);
|
|
66
|
+
}, opts.timeout);
|
|
67
|
+
}
|
|
68
|
+
pendingExtensionRequests.set(id, {
|
|
69
|
+
resolve: (response) => {
|
|
70
|
+
cleanup();
|
|
71
|
+
resolve(parseResponse(response));
|
|
72
|
+
},
|
|
73
|
+
reject,
|
|
74
|
+
});
|
|
75
|
+
output({ type: "extension_ui_request", id, ...request });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create an extension UI context that uses the RPC protocol.
|
|
80
|
+
*/
|
|
81
|
+
const createExtensionUIContext = () => ({
|
|
82
|
+
select: (title, options, opts) => createDialogPromise(opts, undefined, { method: "select", title, options, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? undefined : "value" in r ? r.value : undefined),
|
|
83
|
+
confirm: (title, message, opts) => createDialogPromise(opts, false, { method: "confirm", title, message, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? false : "confirmed" in r ? r.confirmed : false),
|
|
84
|
+
input: (title, placeholder, opts) => createDialogPromise(opts, undefined, { method: "input", title, placeholder, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? undefined : "value" in r ? r.value : undefined),
|
|
85
|
+
notify(message, type) {
|
|
86
|
+
// Fire and forget - no response needed
|
|
87
|
+
output({
|
|
88
|
+
type: "extension_ui_request",
|
|
89
|
+
id: crypto.randomUUID(),
|
|
90
|
+
method: "notify",
|
|
91
|
+
message,
|
|
92
|
+
notifyType: type,
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
onTerminalInput() {
|
|
96
|
+
// Raw terminal input not supported in RPC mode
|
|
97
|
+
return () => { };
|
|
98
|
+
},
|
|
99
|
+
setStatus(key, text) {
|
|
100
|
+
// Fire and forget - no response needed
|
|
101
|
+
output({
|
|
102
|
+
type: "extension_ui_request",
|
|
103
|
+
id: crypto.randomUUID(),
|
|
104
|
+
method: "setStatus",
|
|
105
|
+
statusKey: key,
|
|
106
|
+
statusText: text,
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
setWorkingMessage(_message) {
|
|
110
|
+
// Working message not supported in RPC mode - requires TUI loader access
|
|
111
|
+
},
|
|
112
|
+
setWorkingVisible(_visible) {
|
|
113
|
+
// Working visibility not supported in RPC mode - requires TUI loader access
|
|
114
|
+
},
|
|
115
|
+
setWorkingIndicator(_options) {
|
|
116
|
+
// Working indicator customization not supported in RPC mode - requires TUI loader access
|
|
117
|
+
},
|
|
118
|
+
setHiddenThinkingLabel(_label) {
|
|
119
|
+
// Hidden thinking label not supported in RPC mode - requires TUI message rendering access
|
|
120
|
+
},
|
|
121
|
+
setWidget(key, content, options) {
|
|
122
|
+
// Only support string arrays in RPC mode - factory functions are ignored
|
|
123
|
+
if (content === undefined || Array.isArray(content)) {
|
|
124
|
+
output({
|
|
125
|
+
type: "extension_ui_request",
|
|
126
|
+
id: crypto.randomUUID(),
|
|
127
|
+
method: "setWidget",
|
|
128
|
+
widgetKey: key,
|
|
129
|
+
widgetLines: content,
|
|
130
|
+
widgetPlacement: options?.placement,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Component factories are not supported in RPC mode - would need TUI access
|
|
134
|
+
},
|
|
135
|
+
setFooter(_factory) {
|
|
136
|
+
// Custom footer not supported in RPC mode - requires TUI access
|
|
137
|
+
},
|
|
138
|
+
setHeader(_factory) {
|
|
139
|
+
// Custom header not supported in RPC mode - requires TUI access
|
|
140
|
+
},
|
|
141
|
+
setTitle(title) {
|
|
142
|
+
// Fire and forget - host can implement terminal title control
|
|
143
|
+
output({
|
|
144
|
+
type: "extension_ui_request",
|
|
145
|
+
id: crypto.randomUUID(),
|
|
146
|
+
method: "setTitle",
|
|
147
|
+
title,
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
async custom() {
|
|
151
|
+
// Custom UI not supported in RPC mode
|
|
152
|
+
return undefined;
|
|
153
|
+
},
|
|
154
|
+
pasteToEditor(text) {
|
|
155
|
+
// Paste handling not supported in RPC mode - falls back to setEditorText
|
|
156
|
+
this.setEditorText(text);
|
|
157
|
+
},
|
|
158
|
+
setEditorText(text) {
|
|
159
|
+
// Fire and forget - host can implement editor control
|
|
160
|
+
output({
|
|
161
|
+
type: "extension_ui_request",
|
|
162
|
+
id: crypto.randomUUID(),
|
|
163
|
+
method: "set_editor_text",
|
|
164
|
+
text,
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
getEditorText() {
|
|
168
|
+
// Synchronous method can't wait for RPC response
|
|
169
|
+
// Host should track editor state locally if needed
|
|
170
|
+
return "";
|
|
171
|
+
},
|
|
172
|
+
async editor(title, prefill) {
|
|
173
|
+
const id = crypto.randomUUID();
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
pendingExtensionRequests.set(id, {
|
|
176
|
+
resolve: (response) => {
|
|
177
|
+
if ("cancelled" in response && response.cancelled) {
|
|
178
|
+
resolve(undefined);
|
|
179
|
+
}
|
|
180
|
+
else if ("value" in response) {
|
|
181
|
+
resolve(response.value);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
resolve(undefined);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
reject,
|
|
188
|
+
});
|
|
189
|
+
output({ type: "extension_ui_request", id, method: "editor", title, prefill });
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
addAutocompleteProvider() {
|
|
193
|
+
// Autocomplete provider composition is not supported in RPC mode
|
|
194
|
+
},
|
|
195
|
+
setEditorComponent() {
|
|
196
|
+
// Custom editor components not supported in RPC mode
|
|
197
|
+
},
|
|
198
|
+
getEditorComponent() {
|
|
199
|
+
// Custom editor components not supported in RPC mode
|
|
200
|
+
return undefined;
|
|
201
|
+
},
|
|
202
|
+
get theme() {
|
|
203
|
+
return theme;
|
|
204
|
+
},
|
|
205
|
+
getAllThemes() {
|
|
206
|
+
return [];
|
|
207
|
+
},
|
|
208
|
+
getTheme(_name) {
|
|
209
|
+
return undefined;
|
|
210
|
+
},
|
|
211
|
+
setTheme(_theme) {
|
|
212
|
+
// Theme switching not supported in RPC mode
|
|
213
|
+
return { success: false, error: "Theme switching not supported in RPC mode" };
|
|
214
|
+
},
|
|
215
|
+
getToolsExpanded() {
|
|
216
|
+
// Tool expansion not supported in RPC mode - no TUI
|
|
217
|
+
return false;
|
|
218
|
+
},
|
|
219
|
+
setToolsExpanded(_expanded) {
|
|
220
|
+
// Tool expansion not supported in RPC mode - no TUI
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
runtimeHost.setRebindSession(async () => {
|
|
224
|
+
await rebindSession();
|
|
225
|
+
});
|
|
226
|
+
const rebindSession = async () => {
|
|
227
|
+
session = runtimeHost.session;
|
|
228
|
+
await session.bindExtensions({
|
|
229
|
+
uiContext: createExtensionUIContext(),
|
|
230
|
+
commandContextActions: {
|
|
231
|
+
waitForIdle: () => session.agent.waitForIdle(),
|
|
232
|
+
newSession: async (options) => runtimeHost.newSession(options),
|
|
233
|
+
fork: async (entryId, forkOptions) => {
|
|
234
|
+
const result = await runtimeHost.fork(entryId, forkOptions);
|
|
235
|
+
return { cancelled: result.cancelled };
|
|
236
|
+
},
|
|
237
|
+
navigateTree: async (targetId, options) => {
|
|
238
|
+
const result = await session.navigateTree(targetId, {
|
|
239
|
+
summarize: options?.summarize,
|
|
240
|
+
customInstructions: options?.customInstructions,
|
|
241
|
+
replaceInstructions: options?.replaceInstructions,
|
|
242
|
+
label: options?.label,
|
|
243
|
+
});
|
|
244
|
+
return { cancelled: result.cancelled };
|
|
245
|
+
},
|
|
246
|
+
switchSession: async (sessionPath, options) => {
|
|
247
|
+
return runtimeHost.switchSession(sessionPath, options);
|
|
248
|
+
},
|
|
249
|
+
reload: async () => {
|
|
250
|
+
await session.reload();
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
shutdownHandler: () => {
|
|
254
|
+
shutdownRequested = true;
|
|
255
|
+
},
|
|
256
|
+
onError: (err) => {
|
|
257
|
+
output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
unsubscribe?.();
|
|
261
|
+
unsubscribe = session.subscribe((event) => {
|
|
262
|
+
output(event);
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
const registerSignalHandlers = () => {
|
|
266
|
+
const signals = ["SIGTERM"];
|
|
267
|
+
if (process.platform !== "win32") {
|
|
268
|
+
signals.push("SIGHUP");
|
|
269
|
+
}
|
|
270
|
+
for (const signal of signals) {
|
|
271
|
+
const handler = () => {
|
|
272
|
+
killTrackedDetachedChildren();
|
|
273
|
+
void shutdown(signal === "SIGHUP" ? 129 : 143);
|
|
274
|
+
};
|
|
275
|
+
process.on(signal, handler);
|
|
276
|
+
signalCleanupHandlers.push(() => process.off(signal, handler));
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
await rebindSession();
|
|
280
|
+
registerSignalHandlers();
|
|
281
|
+
// Handle a single command
|
|
282
|
+
const handleCommand = async (command) => {
|
|
283
|
+
const id = command.id;
|
|
284
|
+
switch (command.type) {
|
|
285
|
+
// =================================================================
|
|
286
|
+
// Prompting
|
|
287
|
+
// =================================================================
|
|
288
|
+
case "prompt": {
|
|
289
|
+
// Start prompt handling immediately, but emit the authoritative response only after
|
|
290
|
+
// prompt preflight succeeds. Queued and immediately handled prompts also count as success.
|
|
291
|
+
let preflightSucceeded = false;
|
|
292
|
+
void session
|
|
293
|
+
.prompt(command.message, {
|
|
294
|
+
images: command.images,
|
|
295
|
+
streamingBehavior: command.streamingBehavior,
|
|
296
|
+
source: "rpc",
|
|
297
|
+
preflightResult: (didSucceed) => {
|
|
298
|
+
if (didSucceed) {
|
|
299
|
+
preflightSucceeded = true;
|
|
300
|
+
output(success(id, "prompt"));
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
.catch((e) => {
|
|
305
|
+
if (!preflightSucceeded) {
|
|
306
|
+
output(error(id, "prompt", e.message));
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
case "steer": {
|
|
312
|
+
await session.steer(command.message, command.images);
|
|
313
|
+
return success(id, "steer");
|
|
314
|
+
}
|
|
315
|
+
case "follow_up": {
|
|
316
|
+
await session.followUp(command.message, command.images);
|
|
317
|
+
return success(id, "follow_up");
|
|
318
|
+
}
|
|
319
|
+
case "abort": {
|
|
320
|
+
await session.abort();
|
|
321
|
+
return success(id, "abort");
|
|
322
|
+
}
|
|
323
|
+
case "new_session": {
|
|
324
|
+
const options = command.parentSession ? { parentSession: command.parentSession } : undefined;
|
|
325
|
+
const result = await runtimeHost.newSession(options);
|
|
326
|
+
if (!result.cancelled) {
|
|
327
|
+
await rebindSession();
|
|
328
|
+
}
|
|
329
|
+
return success(id, "new_session", result);
|
|
330
|
+
}
|
|
331
|
+
// =================================================================
|
|
332
|
+
// State
|
|
333
|
+
// =================================================================
|
|
334
|
+
case "get_state": {
|
|
335
|
+
const state = {
|
|
336
|
+
model: session.model,
|
|
337
|
+
thinkingLevel: session.thinkingLevel,
|
|
338
|
+
isStreaming: session.isStreaming,
|
|
339
|
+
isCompacting: session.isCompacting,
|
|
340
|
+
steeringMode: session.steeringMode,
|
|
341
|
+
followUpMode: session.followUpMode,
|
|
342
|
+
sessionFile: session.sessionFile,
|
|
343
|
+
sessionId: session.sessionId,
|
|
344
|
+
sessionName: session.sessionName,
|
|
345
|
+
autoCompactionEnabled: session.autoCompactionEnabled,
|
|
346
|
+
messageCount: session.messages.length,
|
|
347
|
+
pendingMessageCount: session.pendingMessageCount,
|
|
348
|
+
};
|
|
349
|
+
return success(id, "get_state", state);
|
|
350
|
+
}
|
|
351
|
+
// =================================================================
|
|
352
|
+
// Model
|
|
353
|
+
// =================================================================
|
|
354
|
+
case "set_model": {
|
|
355
|
+
const models = await session.modelRegistry.getAvailable();
|
|
356
|
+
const model = models.find((m) => m.provider === command.provider && m.id === command.modelId);
|
|
357
|
+
if (!model) {
|
|
358
|
+
return error(id, "set_model", `Model not found: ${command.provider}/${command.modelId}`);
|
|
359
|
+
}
|
|
360
|
+
await session.setModel(model);
|
|
361
|
+
return success(id, "set_model", model);
|
|
362
|
+
}
|
|
363
|
+
case "cycle_model": {
|
|
364
|
+
const result = await session.cycleModel();
|
|
365
|
+
if (!result) {
|
|
366
|
+
return success(id, "cycle_model", null);
|
|
367
|
+
}
|
|
368
|
+
return success(id, "cycle_model", result);
|
|
369
|
+
}
|
|
370
|
+
case "get_available_models": {
|
|
371
|
+
const models = await session.modelRegistry.getAvailable();
|
|
372
|
+
return success(id, "get_available_models", { models });
|
|
373
|
+
}
|
|
374
|
+
// =================================================================
|
|
375
|
+
// Thinking
|
|
376
|
+
// =================================================================
|
|
377
|
+
case "set_thinking_level": {
|
|
378
|
+
session.setThinkingLevel(command.level);
|
|
379
|
+
return success(id, "set_thinking_level");
|
|
380
|
+
}
|
|
381
|
+
case "cycle_thinking_level": {
|
|
382
|
+
const level = session.cycleThinkingLevel();
|
|
383
|
+
if (!level) {
|
|
384
|
+
return success(id, "cycle_thinking_level", null);
|
|
385
|
+
}
|
|
386
|
+
return success(id, "cycle_thinking_level", { level });
|
|
387
|
+
}
|
|
388
|
+
// =================================================================
|
|
389
|
+
// Queue Modes
|
|
390
|
+
// =================================================================
|
|
391
|
+
case "set_steering_mode": {
|
|
392
|
+
session.setSteeringMode(command.mode);
|
|
393
|
+
return success(id, "set_steering_mode");
|
|
394
|
+
}
|
|
395
|
+
case "set_follow_up_mode": {
|
|
396
|
+
session.setFollowUpMode(command.mode);
|
|
397
|
+
return success(id, "set_follow_up_mode");
|
|
398
|
+
}
|
|
399
|
+
// =================================================================
|
|
400
|
+
// Compaction
|
|
401
|
+
// =================================================================
|
|
402
|
+
case "compact": {
|
|
403
|
+
const result = await session.compact(command.customInstructions);
|
|
404
|
+
return success(id, "compact", result);
|
|
405
|
+
}
|
|
406
|
+
case "set_auto_compaction": {
|
|
407
|
+
session.setAutoCompactionEnabled(command.enabled);
|
|
408
|
+
return success(id, "set_auto_compaction");
|
|
409
|
+
}
|
|
410
|
+
// =================================================================
|
|
411
|
+
// Retry
|
|
412
|
+
// =================================================================
|
|
413
|
+
case "set_auto_retry": {
|
|
414
|
+
session.setAutoRetryEnabled(command.enabled);
|
|
415
|
+
return success(id, "set_auto_retry");
|
|
416
|
+
}
|
|
417
|
+
case "abort_retry": {
|
|
418
|
+
session.abortRetry();
|
|
419
|
+
return success(id, "abort_retry");
|
|
420
|
+
}
|
|
421
|
+
// =================================================================
|
|
422
|
+
// Bash
|
|
423
|
+
// =================================================================
|
|
424
|
+
case "bash": {
|
|
425
|
+
const result = await session.executeBash(command.command);
|
|
426
|
+
return success(id, "bash", result);
|
|
427
|
+
}
|
|
428
|
+
case "abort_bash": {
|
|
429
|
+
session.abortBash();
|
|
430
|
+
return success(id, "abort_bash");
|
|
431
|
+
}
|
|
432
|
+
// =================================================================
|
|
433
|
+
// Session
|
|
434
|
+
// =================================================================
|
|
435
|
+
case "get_session_stats": {
|
|
436
|
+
const stats = session.getSessionStats();
|
|
437
|
+
return success(id, "get_session_stats", stats);
|
|
438
|
+
}
|
|
439
|
+
case "export_html": {
|
|
440
|
+
const path = await session.exportToHtml(command.outputPath);
|
|
441
|
+
return success(id, "export_html", { path });
|
|
442
|
+
}
|
|
443
|
+
case "switch_session": {
|
|
444
|
+
const result = await runtimeHost.switchSession(command.sessionPath);
|
|
445
|
+
if (!result.cancelled) {
|
|
446
|
+
await rebindSession();
|
|
447
|
+
}
|
|
448
|
+
return success(id, "switch_session", result);
|
|
449
|
+
}
|
|
450
|
+
case "fork": {
|
|
451
|
+
const result = await runtimeHost.fork(command.entryId);
|
|
452
|
+
if (!result.cancelled) {
|
|
453
|
+
await rebindSession();
|
|
454
|
+
}
|
|
455
|
+
return success(id, "fork", { text: result.selectedText, cancelled: result.cancelled });
|
|
456
|
+
}
|
|
457
|
+
case "clone": {
|
|
458
|
+
const leafId = session.sessionManager.getLeafId();
|
|
459
|
+
if (!leafId) {
|
|
460
|
+
return error(id, "clone", "Cannot clone session: no current entry selected");
|
|
461
|
+
}
|
|
462
|
+
const result = await runtimeHost.fork(leafId, { position: "at" });
|
|
463
|
+
if (!result.cancelled) {
|
|
464
|
+
await rebindSession();
|
|
465
|
+
}
|
|
466
|
+
return success(id, "clone", { cancelled: result.cancelled });
|
|
467
|
+
}
|
|
468
|
+
case "get_fork_messages": {
|
|
469
|
+
const messages = session.getUserMessagesForForking();
|
|
470
|
+
return success(id, "get_fork_messages", { messages });
|
|
471
|
+
}
|
|
472
|
+
case "get_last_assistant_text": {
|
|
473
|
+
const text = session.getLastAssistantText();
|
|
474
|
+
return success(id, "get_last_assistant_text", { text });
|
|
475
|
+
}
|
|
476
|
+
case "set_session_name": {
|
|
477
|
+
const name = command.name.trim();
|
|
478
|
+
if (!name) {
|
|
479
|
+
return error(id, "set_session_name", "Session name cannot be empty");
|
|
480
|
+
}
|
|
481
|
+
session.setSessionName(name);
|
|
482
|
+
return success(id, "set_session_name");
|
|
483
|
+
}
|
|
484
|
+
// =================================================================
|
|
485
|
+
// Messages
|
|
486
|
+
// =================================================================
|
|
487
|
+
case "get_messages": {
|
|
488
|
+
return success(id, "get_messages", { messages: session.messages });
|
|
489
|
+
}
|
|
490
|
+
// =================================================================
|
|
491
|
+
// Commands (available for invocation via prompt)
|
|
492
|
+
// =================================================================
|
|
493
|
+
case "get_commands": {
|
|
494
|
+
const commands = [];
|
|
495
|
+
for (const command of session.extensionRunner.getRegisteredCommands()) {
|
|
496
|
+
commands.push({
|
|
497
|
+
name: command.invocationName,
|
|
498
|
+
description: command.description,
|
|
499
|
+
source: "extension",
|
|
500
|
+
sourceInfo: command.sourceInfo,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
for (const template of session.promptTemplates) {
|
|
504
|
+
commands.push({
|
|
505
|
+
name: template.name,
|
|
506
|
+
description: template.description,
|
|
507
|
+
source: "prompt",
|
|
508
|
+
sourceInfo: template.sourceInfo,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
for (const skill of session.resourceLoader.getSkills().skills) {
|
|
512
|
+
commands.push({
|
|
513
|
+
name: `skill:${skill.name}`,
|
|
514
|
+
description: skill.description,
|
|
515
|
+
source: "skill",
|
|
516
|
+
sourceInfo: skill.sourceInfo,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return success(id, "get_commands", { commands });
|
|
520
|
+
}
|
|
521
|
+
default: {
|
|
522
|
+
const unknownCommand = command;
|
|
523
|
+
return error(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
/**
|
|
528
|
+
* Check if shutdown was requested and perform shutdown if so.
|
|
529
|
+
* Called after handling each command when waiting for the next command.
|
|
530
|
+
*/
|
|
531
|
+
let detachInput = () => { };
|
|
532
|
+
async function shutdown(exitCode = 0) {
|
|
533
|
+
if (shuttingDown) {
|
|
534
|
+
process.exit(exitCode);
|
|
535
|
+
}
|
|
536
|
+
shuttingDown = true;
|
|
537
|
+
for (const cleanup of signalCleanupHandlers) {
|
|
538
|
+
cleanup();
|
|
539
|
+
}
|
|
540
|
+
unsubscribe?.();
|
|
541
|
+
await runtimeHost.dispose();
|
|
542
|
+
detachInput();
|
|
543
|
+
process.stdin.pause();
|
|
544
|
+
process.exit(exitCode);
|
|
545
|
+
}
|
|
546
|
+
async function checkShutdownRequested() {
|
|
547
|
+
if (!shutdownRequested)
|
|
548
|
+
return;
|
|
549
|
+
await shutdown();
|
|
550
|
+
}
|
|
551
|
+
const handleInputLine = async (line) => {
|
|
552
|
+
let parsed;
|
|
553
|
+
try {
|
|
554
|
+
parsed = JSON.parse(line);
|
|
555
|
+
}
|
|
556
|
+
catch (parseError) {
|
|
557
|
+
output(error(undefined, "parse", `Failed to parse command: ${parseError instanceof Error ? parseError.message : String(parseError)}`));
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
// Handle extension UI responses
|
|
561
|
+
if (typeof parsed === "object" &&
|
|
562
|
+
parsed !== null &&
|
|
563
|
+
"type" in parsed &&
|
|
564
|
+
parsed.type === "extension_ui_response") {
|
|
565
|
+
const response = parsed;
|
|
566
|
+
const pending = pendingExtensionRequests.get(response.id);
|
|
567
|
+
if (pending) {
|
|
568
|
+
pendingExtensionRequests.delete(response.id);
|
|
569
|
+
pending.resolve(response);
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const command = parsed;
|
|
574
|
+
try {
|
|
575
|
+
const response = await handleCommand(command);
|
|
576
|
+
if (response) {
|
|
577
|
+
output(response);
|
|
578
|
+
}
|
|
579
|
+
await checkShutdownRequested();
|
|
580
|
+
}
|
|
581
|
+
catch (commandError) {
|
|
582
|
+
output(error(command.id, command.type, commandError instanceof Error ? commandError.message : String(commandError)));
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const onInputEnd = () => {
|
|
586
|
+
void shutdown();
|
|
587
|
+
};
|
|
588
|
+
process.stdin.on("end", onInputEnd);
|
|
589
|
+
detachInput = (() => {
|
|
590
|
+
const detachJsonl = attachJsonlLineReader(process.stdin, (line) => {
|
|
591
|
+
void handleInputLine(line);
|
|
592
|
+
});
|
|
593
|
+
return () => {
|
|
594
|
+
detachJsonl();
|
|
595
|
+
process.stdin.off("end", onInputEnd);
|
|
596
|
+
};
|
|
597
|
+
})();
|
|
598
|
+
// Keep process alive forever
|
|
599
|
+
return new Promise(() => { });
|
|
600
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Portions of this file are derived from:
|
|
3
|
+
* - ansi-regex (https://github.com/chalk/ansi-regex)
|
|
4
|
+
* - strip-ansi (https://github.com/chalk/strip-ansi)
|
|
5
|
+
*
|
|
6
|
+
* MIT License
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
9
|
+
*
|
|
10
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
* in the Software without restriction, including without limitation the rights
|
|
13
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
* furnished to do so, subject to the following conditions:
|
|
16
|
+
*
|
|
17
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
* copies or substantial portions of the Software.
|
|
19
|
+
*
|
|
20
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
* SOFTWARE.
|
|
27
|
+
*/
|
|
28
|
+
function ansiRegex({ onlyFirst = false } = {}) {
|
|
29
|
+
// Valid string terminator sequences are BEL, ESC\, and 0x9c
|
|
30
|
+
const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
|
|
31
|
+
// OSC sequences only: ESC ] ... ST (non-greedy until the first ST)
|
|
32
|
+
const osc = `(?:\\u001B\\][\\s\\S]*?${ST})`;
|
|
33
|
+
// CSI and related: ESC/C1, optional intermediates, optional params (supports ; and :) then final byte
|
|
34
|
+
const csi = "[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]";
|
|
35
|
+
const pattern = `${osc}|${csi}`;
|
|
36
|
+
return new RegExp(pattern, onlyFirst ? undefined : "g");
|
|
37
|
+
}
|
|
38
|
+
const regex = ansiRegex();
|
|
39
|
+
export function stripAnsi(value) {
|
|
40
|
+
if (typeof value !== "string") {
|
|
41
|
+
throw new TypeError(`Expected a \`string\`, got \`${typeof value}\``);
|
|
42
|
+
}
|
|
43
|
+
// Fast path: ANSI codes require ESC (7-bit) or CSI (8-bit) introducer
|
|
44
|
+
if (!value.includes("\u001B") && !value.includes("\u009B")) {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
// Even though the regex is global, we don't need to reset the `.lastIndex`
|
|
48
|
+
// because unlike `.exec()` and `.test()`, `.replace()` does it automatically
|
|
49
|
+
// and doing it manually has a performance penalty.
|
|
50
|
+
return value.replace(regex, "");
|
|
51
|
+
}
|