@cjhyy/code-shell-core 0.5.0-rc.0 → 0.5.0-rc.2
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/dist/agent/agent-definition-registry.d.ts +29 -0
- package/dist/agent/agent-definition-registry.js +74 -0
- package/dist/agent/agent-definition.d.ts +37 -0
- package/dist/agent/agent-definition.js +59 -0
- package/dist/arena/arena.js +11 -12
- package/dist/arena/context/context-tools.js +2 -4
- package/dist/arena/context/within-root.d.ts +7 -0
- package/dist/arena/context/within-root.js +15 -0
- package/dist/arena/iterate/phases/argue.js +8 -2
- package/dist/arena/iterate/phases/revise.js +1 -1
- package/dist/arena/iterate/phases/tournament.js +3 -3
- package/dist/arena/phases/adjudication.js +1 -4
- package/dist/arena/phases/build-consensus.js +1 -4
- package/dist/arena/phases/cross-review.js +2 -8
- package/dist/arena/phases/debate-rounds.js +1 -4
- package/dist/arena/phases/participant-research.js +1 -4
- package/dist/arena/phases/planning-detail-expansion.js +8 -10
- package/dist/arena/planner.js +0 -1
- package/dist/arena/providers/docs.d.ts +1 -0
- package/dist/arena/providers/docs.js +5 -2
- package/dist/arena/providers/repo.d.ts +1 -0
- package/dist/arena/providers/repo.js +5 -2
- package/dist/arena/strategies/utils.js +36 -3
- package/dist/arena/transitions.d.ts +7 -2
- package/dist/arena/transitions.js +9 -4
- package/dist/arena/types.d.ts +8 -2
- package/dist/automation/cron-expr.d.ts +31 -0
- package/dist/automation/cron-expr.js +151 -0
- package/dist/automation/index.d.ts +41 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/runner.d.ts +67 -0
- package/dist/automation/runner.js +56 -0
- package/dist/automation/scheduler.d.ts +156 -0
- package/dist/automation/scheduler.js +546 -0
- package/dist/automation/store.d.ts +39 -0
- package/dist/automation/store.js +119 -0
- package/dist/automation/write-policy.d.ts +34 -0
- package/dist/automation/write-policy.js +113 -0
- package/dist/automation/write-run.d.ts +51 -0
- package/dist/automation/write-run.js +38 -0
- package/dist/capability-control/index.d.ts +5 -0
- package/dist/capability-control/index.js +3 -0
- package/dist/capability-control/overlay.d.ts +49 -0
- package/dist/capability-control/overlay.js +89 -0
- package/dist/capability-control/project.d.ts +65 -0
- package/dist/capability-control/project.js +147 -0
- package/dist/capability-control/service.d.ts +70 -0
- package/dist/capability-control/service.js +147 -0
- package/dist/capability-control/types.d.ts +62 -0
- package/dist/capability-control/types.js +17 -0
- package/dist/cli/agent-server-stdio.d.ts +12 -1
- package/dist/cli/agent-server-stdio.js +118 -18
- package/dist/cli/agent-server-tcp.d.ts +20 -0
- package/dist/cli/agent-server-tcp.js +120 -0
- package/dist/cli/graceful-shutdown.d.ts +27 -0
- package/dist/cli/graceful-shutdown.js +30 -0
- package/dist/context/compaction.js +23 -15
- package/dist/context/manager.d.ts +0 -20
- package/dist/context/manager.js +0 -37
- package/dist/cron/cron-runtime.d.ts +2 -0
- package/dist/cron/cron-runtime.js +2 -0
- package/dist/cron/cron-store.d.ts +2 -0
- package/dist/cron/cron-store.js +2 -0
- package/dist/cron/scheduler.d.ts +5 -32
- package/dist/cron/scheduler.js +5 -113
- package/dist/data/openrouter-sync.js +1 -1
- package/dist/engine/engine.d.ts +256 -16
- package/dist/engine/engine.js +856 -200
- package/dist/engine/goal.d.ts +42 -0
- package/dist/engine/goal.js +52 -0
- package/dist/engine/image-compression.d.ts +81 -0
- package/dist/engine/image-compression.js +186 -0
- package/dist/engine/image-policy.d.ts +152 -0
- package/dist/engine/image-policy.js +165 -0
- package/dist/engine/reactive-threshold.d.ts +13 -0
- package/dist/engine/reactive-threshold.js +17 -0
- package/dist/engine/runtime.js +7 -0
- package/dist/engine/session-title.d.ts +13 -0
- package/dist/engine/session-title.js +34 -0
- package/dist/engine/turn-loop.d.ts +29 -8
- package/dist/engine/turn-loop.js +498 -316
- package/dist/git/parse-log.d.ts +13 -0
- package/dist/git/parse-log.js +21 -0
- package/dist/git/utils.d.ts +8 -6
- package/dist/git/utils.js +57 -26
- package/dist/git/worktree.js +33 -18
- package/dist/hooks/events.d.ts +23 -1
- package/dist/hooks/goal-stop-hook.d.ts +30 -0
- package/dist/hooks/goal-stop-hook.js +102 -0
- package/dist/hooks/hook-output.d.ts +33 -0
- package/dist/hooks/hook-output.js +79 -0
- package/dist/hooks/registry.d.ts +7 -0
- package/dist/hooks/registry.js +38 -1
- package/dist/hooks/shell-runner.d.ts +2 -0
- package/dist/hooks/shell-runner.js +43 -2
- package/dist/index.d.ts +32 -7
- package/dist/index.js +30 -3
- package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
- package/dist/llm/capabilities/reasoning-control.js +38 -0
- package/dist/llm/capabilities/rules.js +33 -3
- package/dist/llm/capabilities/types.d.ts +22 -2
- package/dist/llm/clamp-max-tokens.d.ts +11 -0
- package/dist/llm/clamp-max-tokens.js +17 -0
- package/dist/llm/client-base.d.ts +41 -4
- package/dist/llm/client-base.js +67 -13
- package/dist/llm/client-factory.d.ts +3 -3
- package/dist/llm/client-factory.js +2 -2
- package/dist/llm/model-pool.d.ts +15 -11
- package/dist/llm/model-pool.js +23 -20
- package/dist/llm/provider-catalog.d.ts +2 -2
- package/dist/llm/providers/anthropic.d.ts +32 -2
- package/dist/llm/providers/anthropic.js +147 -8
- package/dist/llm/providers/openai.d.ts +11 -2
- package/dist/llm/providers/openai.js +253 -52
- package/dist/llm/reasoning-setting.d.ts +51 -0
- package/dist/llm/reasoning-setting.js +32 -0
- package/dist/llm/stop-reason.d.ts +13 -0
- package/dist/llm/stop-reason.js +21 -0
- package/dist/llm/strip-vision.d.ts +32 -0
- package/dist/llm/strip-vision.js +94 -0
- package/dist/llm/types.d.ts +5 -6
- package/dist/logging/logger.js +18 -4
- package/dist/logging/sanitize-messages.d.ts +10 -0
- package/dist/logging/sanitize-messages.js +90 -0
- package/dist/lsp/client.js +28 -8
- package/dist/lsp/manager.js +2 -1
- package/dist/lsp/root-path.d.ts +9 -0
- package/dist/lsp/root-path.js +12 -0
- package/dist/onboarding.js +18 -11
- package/dist/plugins/gitOps.d.ts +34 -0
- package/dist/plugins/gitOps.js +59 -3
- package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
- package/dist/plugins/installer/codex/convertAgents.js +55 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
- package/dist/plugins/installer/codex/convertMcp.js +45 -0
- package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
- package/dist/plugins/installer/codex/convertSkills.js +33 -0
- package/dist/plugins/installer/detectFormat.d.ts +2 -0
- package/dist/plugins/installer/detectFormat.js +6 -0
- package/dist/plugins/installer/install.d.ts +7 -0
- package/dist/plugins/installer/install.js +99 -0
- package/dist/plugins/installer/installFromSource.d.ts +13 -0
- package/dist/plugins/installer/installFromSource.js +45 -0
- package/dist/plugins/installer/list.d.ts +9 -0
- package/dist/plugins/installer/list.js +30 -0
- package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
- package/dist/plugins/installer/loadPluginAgents.js +23 -0
- package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
- package/dist/plugins/installer/loadPluginMcp.js +74 -0
- package/dist/plugins/installer/parseSource.d.ts +20 -0
- package/dist/plugins/installer/parseSource.js +74 -0
- package/dist/plugins/installer/paths.d.ts +5 -0
- package/dist/plugins/installer/paths.js +25 -0
- package/dist/plugins/installer/types.d.ts +49 -0
- package/dist/plugins/installer/types.js +27 -0
- package/dist/plugins/installer/uninstall.d.ts +2 -0
- package/dist/plugins/installer/uninstall.js +14 -0
- package/dist/plugins/installer/update.d.ts +14 -0
- package/dist/plugins/installer/update.js +65 -0
- package/dist/plugins/loadPluginHooks.d.ts +8 -1
- package/dist/plugins/loadPluginHooks.js +11 -1
- package/dist/plugins/marketplaceManager.d.ts +8 -1
- package/dist/plugins/marketplaceManager.js +38 -4
- package/dist/plugins/pluginCommandHook.js +34 -0
- package/dist/plugins/pluginInstaller.d.ts +21 -0
- package/dist/plugins/pluginInstaller.js +128 -29
- package/dist/plugins/schemas.js +40 -6
- package/dist/plugins/types.d.ts +4 -0
- package/dist/preset/index.js +16 -0
- package/dist/prompt/composer.d.ts +10 -0
- package/dist/prompt/composer.js +29 -4
- package/dist/prompt/sections/orchestration.md +8 -0
- package/dist/protocol/chat-session-manager.d.ts +8 -1
- package/dist/protocol/chat-session-manager.js +10 -0
- package/dist/protocol/chat-session.d.ts +17 -0
- package/dist/protocol/chat-session.js +28 -0
- package/dist/protocol/redact.d.ts +50 -0
- package/dist/protocol/redact.js +71 -0
- package/dist/protocol/server.d.ts +41 -2
- package/dist/protocol/server.js +231 -33
- package/dist/protocol/tcp-transport.d.ts +45 -0
- package/dist/protocol/tcp-transport.js +74 -0
- package/dist/protocol/types.d.ts +23 -0
- package/dist/protocol/types.js +6 -0
- package/dist/remote/bridge.d.ts +11 -2
- package/dist/remote/bridge.js +61 -33
- package/dist/run/ArtifactTracker.js +5 -4
- package/dist/run/EngineRunner.d.ts +27 -1
- package/dist/run/EngineRunner.js +46 -8
- package/dist/run/FileRunStore.js +32 -8
- package/dist/run/RunLock.d.ts +28 -2
- package/dist/run/RunLock.js +49 -7
- package/dist/run/RunManager.js +51 -3
- package/dist/run/RunQueue.d.ts +1 -0
- package/dist/run/RunQueue.js +8 -2
- package/dist/run/factory.d.ts +7 -0
- package/dist/run/factory.js +1 -0
- package/dist/run/index.d.ts +2 -2
- package/dist/run/index.js +1 -1
- package/dist/run/redirect-target.d.ts +7 -0
- package/dist/run/redirect-target.js +13 -0
- package/dist/run/types.js +1 -1
- package/dist/services/browser-open.d.ts +13 -0
- package/dist/services/browser-open.js +16 -0
- package/dist/services/dream-consolidation.d.ts +57 -0
- package/dist/services/dream-consolidation.js +151 -0
- package/dist/services/memory-orchestrator.js +18 -0
- package/dist/services/notifier.d.ts +8 -0
- package/dist/services/notifier.js +39 -25
- package/dist/services/oauth.js +5 -7
- package/dist/services/session-memory-sort.d.ts +8 -0
- package/dist/services/session-memory-sort.js +9 -0
- package/dist/services/session-memory.js +8 -9
- package/dist/session/file-history.js +10 -3
- package/dist/session/session-manager.d.ts +23 -1
- package/dist/session/session-manager.js +73 -2
- package/dist/settings/disk-defaults.d.ts +35 -0
- package/dist/settings/disk-defaults.js +24 -0
- package/dist/settings/manager.d.ts +45 -1
- package/dist/settings/manager.js +121 -17
- package/dist/settings/manager.test.d.ts +1 -0
- package/dist/settings/manager.test.js +73 -0
- package/dist/settings/personalization.d.ts +19 -0
- package/dist/settings/personalization.js +7 -0
- package/dist/settings/schema.d.ts +1163 -126
- package/dist/settings/schema.js +126 -10
- package/dist/skills/scanner.d.ts +13 -6
- package/dist/skills/scanner.js +23 -4
- package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
- package/dist/tool-system/builtin/add-marketplace.js +76 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +15 -0
- package/dist/tool-system/builtin/agent-registry.js +14 -4
- package/dist/tool-system/builtin/agent.d.ts +55 -0
- package/dist/tool-system/builtin/agent.js +165 -17
- package/dist/tool-system/builtin/apply-patch/index.js +22 -3
- package/dist/tool-system/builtin/arena.js +6 -7
- package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
- package/dist/tool-system/builtin/complete-goal.js +45 -0
- package/dist/tool-system/builtin/cron.js +53 -9
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +6 -1
- package/dist/tool-system/builtin/generate-image.d.ts +25 -0
- package/dist/tool-system/builtin/generate-image.js +146 -0
- package/dist/tool-system/builtin/glob.js +7 -0
- package/dist/tool-system/builtin/grep.js +6 -0
- package/dist/tool-system/builtin/index.d.ts +18 -1
- package/dist/tool-system/builtin/index.js +62 -1
- package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
- package/dist/tool-system/builtin/notebook-edit.js +8 -1
- package/dist/tool-system/builtin/read.d.ts +2 -1
- package/dist/tool-system/builtin/read.js +9 -1
- package/dist/tool-system/builtin/skill.js +23 -5
- package/dist/tool-system/builtin/sleep.js +9 -3
- package/dist/tool-system/builtin/task.js +4 -1
- package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
- package/dist/tool-system/builtin/update-automation-memory.js +60 -0
- package/dist/tool-system/builtin/view-image.d.ts +18 -0
- package/dist/tool-system/builtin/view-image.js +96 -0
- package/dist/tool-system/builtin/web-search.d.ts +4 -1
- package/dist/tool-system/builtin/web-search.js +36 -3
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +7 -1
- package/dist/tool-system/context.d.ts +43 -0
- package/dist/tool-system/executor.d.ts +0 -1
- package/dist/tool-system/executor.js +34 -40
- package/dist/tool-system/investigation-guard.d.ts +4 -1
- package/dist/tool-system/investigation-guard.js +19 -3
- package/dist/tool-system/mcp-manager.d.ts +36 -12
- package/dist/tool-system/mcp-manager.js +173 -10
- package/dist/tool-system/path-policy.d.ts +94 -0
- package/dist/tool-system/path-policy.js +279 -0
- package/dist/tool-system/permission.js +43 -50
- package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
- package/dist/tool-system/plan-mode-allowlist.js +43 -0
- package/dist/tool-system/registry.js +12 -1
- package/dist/tool-system/validation.d.ts +10 -0
- package/dist/tool-system/validation.js +16 -3
- package/dist/types.d.ts +90 -15
- package/dist/utils/format.js +4 -2
- package/dist/utils/lockfile.js +9 -2
- package/dist/utils/theme.d.ts +6 -0
- package/dist/utils/theme.js +4 -2
- package/package.json +2 -1
package/dist/run/RunQueue.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type RunQueueExecutor = (runId: string) => Promise<void>;
|
|
|
11
11
|
export declare class RunQueue {
|
|
12
12
|
private readonly concurrency;
|
|
13
13
|
private readonly pending;
|
|
14
|
+
private readonly pendingSet;
|
|
14
15
|
private readonly active;
|
|
15
16
|
private executor;
|
|
16
17
|
private draining;
|
package/dist/run/RunQueue.js
CHANGED
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export class RunQueue {
|
|
8
8
|
concurrency;
|
|
9
|
+
// `pending` keeps FIFO order; `pendingSet` mirrors its membership for O(1)
|
|
10
|
+
// dedup/lookup (the array's includes() was O(n)).
|
|
9
11
|
pending = [];
|
|
12
|
+
pendingSet = new Set();
|
|
10
13
|
active = new Set();
|
|
11
14
|
executor = null;
|
|
12
15
|
draining = false;
|
|
@@ -17,15 +20,17 @@ export class RunQueue {
|
|
|
17
20
|
this.executor = fn;
|
|
18
21
|
}
|
|
19
22
|
enqueue(runId) {
|
|
20
|
-
if (this.
|
|
23
|
+
if (this.pendingSet.has(runId) || this.active.has(runId))
|
|
21
24
|
return;
|
|
22
25
|
this.pending.push(runId);
|
|
26
|
+
this.pendingSet.add(runId);
|
|
23
27
|
this.drain();
|
|
24
28
|
}
|
|
25
29
|
cancel(runId) {
|
|
26
30
|
const idx = this.pending.indexOf(runId);
|
|
27
31
|
if (idx !== -1) {
|
|
28
32
|
this.pending.splice(idx, 1);
|
|
33
|
+
this.pendingSet.delete(runId);
|
|
29
34
|
return true;
|
|
30
35
|
}
|
|
31
36
|
return false;
|
|
@@ -34,7 +39,7 @@ export class RunQueue {
|
|
|
34
39
|
return this.active.has(runId);
|
|
35
40
|
}
|
|
36
41
|
isPending(runId) {
|
|
37
|
-
return this.
|
|
42
|
+
return this.pendingSet.has(runId);
|
|
38
43
|
}
|
|
39
44
|
get activeCount() {
|
|
40
45
|
return this.active.size;
|
|
@@ -57,6 +62,7 @@ export class RunQueue {
|
|
|
57
62
|
return;
|
|
58
63
|
while (this.active.size < this.concurrency && this.pending.length > 0) {
|
|
59
64
|
const runId = this.pending.shift();
|
|
65
|
+
this.pendingSet.delete(runId);
|
|
60
66
|
this.active.add(runId);
|
|
61
67
|
this.executor(runId)
|
|
62
68
|
.catch((err) => {
|
package/dist/run/factory.d.ts
CHANGED
|
@@ -56,6 +56,13 @@ export interface CreateRunManagerOptions {
|
|
|
56
56
|
defaultTags?: string[];
|
|
57
57
|
/** Metadata merged into every submitted run. Submit input wins on conflicts. */
|
|
58
58
|
defaultMetadata?: Record<string, unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* Override the approval backend for every run. Used by unattended hosts
|
|
61
|
+
* (automation/cron) that must auto-decide without a UI — e.g.
|
|
62
|
+
* `new HeadlessApprovalBackend("approve-read-only")`. When unset, runs use
|
|
63
|
+
* the interactive run-aware backend.
|
|
64
|
+
*/
|
|
65
|
+
approvalBackend?: import("../tool-system/permission.js").ApprovalBackend;
|
|
59
66
|
}
|
|
60
67
|
/**
|
|
61
68
|
* Create a fully configured RunManager with one call.
|
package/dist/run/factory.js
CHANGED
|
@@ -41,6 +41,7 @@ export function createRunManager(options) {
|
|
|
41
41
|
customSystemPrompt: options.customSystemPrompt,
|
|
42
42
|
appendSystemPrompt: options.appendSystemPrompt,
|
|
43
43
|
hooks: options.hooks,
|
|
44
|
+
...(options.approvalBackend ? { approvalBackend: options.approvalBackend } : {}),
|
|
44
45
|
},
|
|
45
46
|
concurrency: options.concurrency ?? 1,
|
|
46
47
|
runsDir,
|
package/dist/run/index.d.ts
CHANGED
|
@@ -9,9 +9,9 @@ export { RunQueue } from "./RunQueue.js";
|
|
|
9
9
|
export { RunApprovalBackend, createRunAskUserFn, type RunLifecycleHooks, type PendingApproval, type PendingInput, } from "./RunApprovalBackend.js";
|
|
10
10
|
export { CheckpointWriter, type CheckpointWriterConfig } from "./CheckpointWriter.js";
|
|
11
11
|
export { ArtifactTracker, type ArtifactTrackerConfig } from "./ArtifactTracker.js";
|
|
12
|
-
export { RunLock, type RunLockConfig } from "./RunLock.js";
|
|
12
|
+
export { RunLock, type RunLockConfig, type RunLockAcquireResult } from "./RunLock.js";
|
|
13
13
|
export { Heartbeat, type HeartbeatConfig, type HeartbeatData } from "./Heartbeat.js";
|
|
14
14
|
export { NoopEvaluator, CompositeEvaluator, type Evaluator, type EvaluatorResult, type EvaluatorVerdict, type EvaluatorContext, } from "./Evaluator.js";
|
|
15
|
-
export { EngineRunner, type EngineRunnerConfig, type RunExecutionHandle, type RunExecutor, type CustomToolEntry, } from "./EngineRunner.js";
|
|
15
|
+
export { EngineRunner, AUTOMATION_PROMPT_NOTE, AUTOMATION_RUN_SOURCE, type EngineRunnerConfig, type RunExecutionHandle, type RunExecutor, type CustomToolEntry, } from "./EngineRunner.js";
|
|
16
16
|
export { RunManager, type RunManagerConfig } from "./RunManager.js";
|
|
17
17
|
export { createRunManager, type CreateRunManagerOptions } from "./factory.js";
|
package/dist/run/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export { Heartbeat } from "./Heartbeat.js";
|
|
|
16
16
|
// Evaluator
|
|
17
17
|
export { NoopEvaluator, CompositeEvaluator, } from "./Evaluator.js";
|
|
18
18
|
// Runner
|
|
19
|
-
export { EngineRunner, } from "./EngineRunner.js";
|
|
19
|
+
export { EngineRunner, AUTOMATION_PROMPT_NOTE, AUTOMATION_RUN_SOURCE, } from "./EngineRunner.js";
|
|
20
20
|
// Manager
|
|
21
21
|
export { RunManager } from "./RunManager.js";
|
|
22
22
|
// Factory
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the file target of a trailing shell redirect (`> file` / `>> file`).
|
|
3
|
+
* Handles quoted paths with spaces — the old /> \s*(\S+)\s*$/ matched only the
|
|
4
|
+
* first non-whitespace run, so `> "my file.txt"` yielded `"my`
|
|
5
|
+
* (review-2026-05-30). Returns undefined when there's no trailing redirect.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseRedirectTarget(command: string): string | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the file target of a trailing shell redirect (`> file` / `>> file`).
|
|
3
|
+
* Handles quoted paths with spaces — the old /> \s*(\S+)\s*$/ matched only the
|
|
4
|
+
* first non-whitespace run, so `> "my file.txt"` yielded `"my`
|
|
5
|
+
* (review-2026-05-30). Returns undefined when there's no trailing redirect.
|
|
6
|
+
*/
|
|
7
|
+
export function parseRedirectTarget(command) {
|
|
8
|
+
// Optional quote captured in group 1; body in group 2 (or unquoted in 3).
|
|
9
|
+
const m = command.match(/>>?\s*(?:(['"])(.*?)\1|(\S+))\s*$/);
|
|
10
|
+
if (!m)
|
|
11
|
+
return undefined;
|
|
12
|
+
return m[2] !== undefined ? m[2] : m[3];
|
|
13
|
+
}
|
package/dist/run/types.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the argv to open a URL in the system browser, per platform. Returned
|
|
3
|
+
* as { cmd, args } for execFile (no shell), so the URL is a single literal
|
|
4
|
+
* argument and can't be shell-interpreted — the old code interpolated it into
|
|
5
|
+
* an exec() shell string (review-2026-05-30, security).
|
|
6
|
+
*
|
|
7
|
+
* Windows uses `cmd /c start "" <url>`: `start`'s first quoted arg is the
|
|
8
|
+
* window title, so an empty title slot keeps the URL from being read as one.
|
|
9
|
+
*/
|
|
10
|
+
export declare function browserOpenCommand(platform: NodeJS.Platform, url: string): {
|
|
11
|
+
cmd: string;
|
|
12
|
+
args: string[];
|
|
13
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the argv to open a URL in the system browser, per platform. Returned
|
|
3
|
+
* as { cmd, args } for execFile (no shell), so the URL is a single literal
|
|
4
|
+
* argument and can't be shell-interpreted — the old code interpolated it into
|
|
5
|
+
* an exec() shell string (review-2026-05-30, security).
|
|
6
|
+
*
|
|
7
|
+
* Windows uses `cmd /c start "" <url>`: `start`'s first quoted arg is the
|
|
8
|
+
* window title, so an empty title slot keeps the URL from being read as one.
|
|
9
|
+
*/
|
|
10
|
+
export function browserOpenCommand(platform, url) {
|
|
11
|
+
if (platform === "darwin")
|
|
12
|
+
return { cmd: "open", args: [url] };
|
|
13
|
+
if (platform === "win32")
|
|
14
|
+
return { cmd: "cmd", args: ["/c", "start", "", url] };
|
|
15
|
+
return { cmd: "xdg-open", args: [url] };
|
|
16
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream consolidation — the LLM tool-call loop that cleans up the `dream`
|
|
3
|
+
* memory scope (deduplicate, merge, drop stale, improve descriptions).
|
|
4
|
+
*
|
|
5
|
+
* This logic used to live as two private methods on Engine
|
|
6
|
+
* (`runDreamLoop` + `dispatchDreamTool`). It was extracted here so it can be
|
|
7
|
+
* driven from two places:
|
|
8
|
+
* - the end-of-session auto-dream pipeline (Engine.runDreamLoop delegates here)
|
|
9
|
+
* - a manual "整理 / Dream" trigger from the desktop host, which constructs a
|
|
10
|
+
* seed Engine purely to obtain a toolRegistry + LLM client and then calls
|
|
11
|
+
* this directly (it never runs a turn).
|
|
12
|
+
*
|
|
13
|
+
* The loop is intentionally small and offline:
|
|
14
|
+
* - No streaming, no UI events — it runs in the background.
|
|
15
|
+
* - No permission prompts — there is no interactive backend on this path, so
|
|
16
|
+
* we hard-reject any attempt to Save/Delete in the "user" scope before
|
|
17
|
+
* dispatching. The "dream" scope is the LLM's workspace and goes through
|
|
18
|
+
* freely.
|
|
19
|
+
* - Capped at MAX_TURNS LLM round-trips and MAX_WRITES total mutations to
|
|
20
|
+
* bound damage on misbehavior.
|
|
21
|
+
*/
|
|
22
|
+
import type { LLMClientBase } from "../llm/client-base.js";
|
|
23
|
+
import type { ToolRegistry } from "../tool-system/registry.js";
|
|
24
|
+
import type { ToolContext } from "../tool-system/context.js";
|
|
25
|
+
export interface DreamConsolidationInput {
|
|
26
|
+
/** LLM client for the consolidation calls. */
|
|
27
|
+
llmClient: LLMClientBase;
|
|
28
|
+
/** Tool registry that must contain the four Memory* tools. */
|
|
29
|
+
toolRegistry: ToolRegistry;
|
|
30
|
+
/**
|
|
31
|
+
* Base tool context. `cwd` is overridden with `projectDir` (or process.cwd())
|
|
32
|
+
* so the memory tools resolve the right memory directory. Callers with an
|
|
33
|
+
* Engine pass `engine.buildToolContext()`.
|
|
34
|
+
*/
|
|
35
|
+
toolContext: ToolContext;
|
|
36
|
+
/** Project root; when set, memories are scoped per-project. */
|
|
37
|
+
projectDir?: string;
|
|
38
|
+
/** For log attribution. */
|
|
39
|
+
sessionId?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface DreamConsolidationResult {
|
|
42
|
+
/** True if the loop ran (with or without writes); false if it bailed early. */
|
|
43
|
+
ran: boolean;
|
|
44
|
+
/** The LLM's final one-paragraph summary of what it changed (may be empty). */
|
|
45
|
+
summary: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Drive the dream-scope consolidation tool-call loop.
|
|
49
|
+
*
|
|
50
|
+
* Loads both scopes (user is read-only context; dream is the workspace),
|
|
51
|
+
* builds the dream prompts, and runs the LLM with a whitelisted subset of
|
|
52
|
+
* memory tools until it stops calling tools or hits the turn cap.
|
|
53
|
+
*
|
|
54
|
+
* Returns `ran: false` if the registry is missing the memory tools (nothing
|
|
55
|
+
* happened); otherwise `ran: true` with the final summary text.
|
|
56
|
+
*/
|
|
57
|
+
export declare function runDreamConsolidation(input: DreamConsolidationInput): Promise<DreamConsolidationResult>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dream consolidation — the LLM tool-call loop that cleans up the `dream`
|
|
3
|
+
* memory scope (deduplicate, merge, drop stale, improve descriptions).
|
|
4
|
+
*
|
|
5
|
+
* This logic used to live as two private methods on Engine
|
|
6
|
+
* (`runDreamLoop` + `dispatchDreamTool`). It was extracted here so it can be
|
|
7
|
+
* driven from two places:
|
|
8
|
+
* - the end-of-session auto-dream pipeline (Engine.runDreamLoop delegates here)
|
|
9
|
+
* - a manual "整理 / Dream" trigger from the desktop host, which constructs a
|
|
10
|
+
* seed Engine purely to obtain a toolRegistry + LLM client and then calls
|
|
11
|
+
* this directly (it never runs a turn).
|
|
12
|
+
*
|
|
13
|
+
* The loop is intentionally small and offline:
|
|
14
|
+
* - No streaming, no UI events — it runs in the background.
|
|
15
|
+
* - No permission prompts — there is no interactive backend on this path, so
|
|
16
|
+
* we hard-reject any attempt to Save/Delete in the "user" scope before
|
|
17
|
+
* dispatching. The "dream" scope is the LLM's workspace and goes through
|
|
18
|
+
* freely.
|
|
19
|
+
* - Capped at MAX_TURNS LLM round-trips and MAX_WRITES total mutations to
|
|
20
|
+
* bound damage on misbehavior.
|
|
21
|
+
*/
|
|
22
|
+
import { MemoryManager } from "../session/memory.js";
|
|
23
|
+
import { buildDreamSystemPrompt, buildDreamUserPrompt, } from "./auto-dream.js";
|
|
24
|
+
import { logger } from "../logging/logger.js";
|
|
25
|
+
const MAX_TURNS = 8;
|
|
26
|
+
const MAX_WRITES = 10;
|
|
27
|
+
const MEMORY_TOOL_NAMES = ["MemoryList", "MemoryRead", "MemorySave", "MemoryDelete"];
|
|
28
|
+
/**
|
|
29
|
+
* Drive the dream-scope consolidation tool-call loop.
|
|
30
|
+
*
|
|
31
|
+
* Loads both scopes (user is read-only context; dream is the workspace),
|
|
32
|
+
* builds the dream prompts, and runs the LLM with a whitelisted subset of
|
|
33
|
+
* memory tools until it stops calling tools or hits the turn cap.
|
|
34
|
+
*
|
|
35
|
+
* Returns `ran: false` if the registry is missing the memory tools (nothing
|
|
36
|
+
* happened); otherwise `ran: true` with the final summary text.
|
|
37
|
+
*/
|
|
38
|
+
export async function runDreamConsolidation(input) {
|
|
39
|
+
const { llmClient, toolRegistry, projectDir, sessionId } = input;
|
|
40
|
+
const memoryTools = MEMORY_TOOL_NAMES
|
|
41
|
+
.map((n) => toolRegistry.getTool(n))
|
|
42
|
+
.filter((t) => t != null);
|
|
43
|
+
if (memoryTools.length < MEMORY_TOOL_NAMES.length) {
|
|
44
|
+
logger.warn("memory.dream_missing_tools", {
|
|
45
|
+
sessionId,
|
|
46
|
+
found: memoryTools.map((t) => t.name),
|
|
47
|
+
});
|
|
48
|
+
return { ran: false, summary: "" };
|
|
49
|
+
}
|
|
50
|
+
// Dream sees BOTH scopes — user/ is read-only context so it can spot
|
|
51
|
+
// duplicates spanning scopes, dream/ is the workspace it edits.
|
|
52
|
+
const mm = new MemoryManager({ projectDir });
|
|
53
|
+
const userMems = mm.loadScope("user");
|
|
54
|
+
const dreamMems = mm.loadScope("dream");
|
|
55
|
+
const systemPrompt = buildDreamSystemPrompt();
|
|
56
|
+
const userPrompt = buildDreamUserPrompt(userMems, dreamMems);
|
|
57
|
+
// Strip RegisteredTool down to the shape createMessage expects.
|
|
58
|
+
const toolDefs = memoryTools.map((t) => ({
|
|
59
|
+
name: t.name,
|
|
60
|
+
description: t.description,
|
|
61
|
+
inputSchema: t.inputSchema,
|
|
62
|
+
}));
|
|
63
|
+
const toolCtx = {
|
|
64
|
+
...input.toolContext,
|
|
65
|
+
cwd: projectDir ?? process.cwd(),
|
|
66
|
+
};
|
|
67
|
+
const messages = [{ role: "user", content: userPrompt }];
|
|
68
|
+
let writeBudget = MAX_WRITES;
|
|
69
|
+
for (let turn = 0; turn < MAX_TURNS; turn++) {
|
|
70
|
+
const resp = await llmClient.createMessage({
|
|
71
|
+
systemPrompt,
|
|
72
|
+
messages,
|
|
73
|
+
tools: toolDefs,
|
|
74
|
+
maxTokens: 2048,
|
|
75
|
+
recordUsage: false,
|
|
76
|
+
reasoning: { mode: "off" },
|
|
77
|
+
});
|
|
78
|
+
if (resp.toolCalls.length === 0) {
|
|
79
|
+
logger.info("memory.dream_finished", {
|
|
80
|
+
sessionId,
|
|
81
|
+
turn,
|
|
82
|
+
finalText: resp.text.slice(0, 500),
|
|
83
|
+
});
|
|
84
|
+
return { ran: true, summary: resp.text };
|
|
85
|
+
}
|
|
86
|
+
// Echo the assistant turn back so subsequent turns see the tool_use ids.
|
|
87
|
+
const assistantContent = [];
|
|
88
|
+
if (resp.text)
|
|
89
|
+
assistantContent.push({ type: "text", text: resp.text });
|
|
90
|
+
for (const tc of resp.toolCalls) {
|
|
91
|
+
assistantContent.push({
|
|
92
|
+
type: "tool_use",
|
|
93
|
+
id: tc.id,
|
|
94
|
+
name: tc.toolName,
|
|
95
|
+
input: tc.args,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
messages.push({ role: "assistant", content: assistantContent });
|
|
99
|
+
// Dispatch every tool call requested in this turn.
|
|
100
|
+
const toolResults = [];
|
|
101
|
+
for (const tc of resp.toolCalls) {
|
|
102
|
+
const result = await dispatchDreamTool(tc, toolRegistry, toolCtx, () => {
|
|
103
|
+
if (writeBudget <= 0)
|
|
104
|
+
return false;
|
|
105
|
+
writeBudget--;
|
|
106
|
+
return true;
|
|
107
|
+
});
|
|
108
|
+
toolResults.push({
|
|
109
|
+
type: "tool_result",
|
|
110
|
+
tool_use_id: tc.id,
|
|
111
|
+
content: result,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
messages.push({ role: "user", content: toolResults });
|
|
115
|
+
}
|
|
116
|
+
logger.warn("memory.dream_hit_turn_cap", { sessionId, maxTurns: MAX_TURNS });
|
|
117
|
+
return { ran: true, summary: "" };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Execute one memory tool call inside the dream loop. Enforces the two
|
|
121
|
+
* dream-loop invariants the prompt also states:
|
|
122
|
+
* - Only the 4 memory tools are dispatchable.
|
|
123
|
+
* - Save/Delete in "user" scope is refused (returned as a tool error)
|
|
124
|
+
* because dream runs without an interactive permission backend.
|
|
125
|
+
*/
|
|
126
|
+
async function dispatchDreamTool(tc, toolRegistry, ctx, consumeWriteBudget) {
|
|
127
|
+
const allowed = new Set(MEMORY_TOOL_NAMES);
|
|
128
|
+
if (!allowed.has(tc.toolName)) {
|
|
129
|
+
return `Error: tool "${tc.toolName}" is not available in the dream loop`;
|
|
130
|
+
}
|
|
131
|
+
const isWrite = tc.toolName === "MemorySave" || tc.toolName === "MemoryDelete";
|
|
132
|
+
if (isWrite) {
|
|
133
|
+
const scope = tc.args?.scope;
|
|
134
|
+
if (scope !== "dream") {
|
|
135
|
+
return (`Error: dream loop may only write to scope "dream", got "${scope}". ` +
|
|
136
|
+
`User-scope changes require interactive permission, which is not available here.`);
|
|
137
|
+
}
|
|
138
|
+
if (!consumeWriteBudget()) {
|
|
139
|
+
return "Error: dream write budget exhausted — stop calling write tools and summarize instead.";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const result = await toolRegistry.executeTool(tc.toolName, tc.args, { ctx });
|
|
144
|
+
if (result.isError)
|
|
145
|
+
return result.error ?? `Error executing ${tc.toolName}`;
|
|
146
|
+
return result.result ?? "";
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
return `Error executing ${tc.toolName}: ${err.message}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -36,10 +36,19 @@ export class MemoryOrchestrator {
|
|
|
36
36
|
// --------------- 1. Extract durable memories ---------------
|
|
37
37
|
let extracted = 0;
|
|
38
38
|
try {
|
|
39
|
+
let t = Date.now();
|
|
39
40
|
const existing = mm.loadAll();
|
|
41
|
+
const loadMs = Date.now() - t;
|
|
42
|
+
t = Date.now();
|
|
40
43
|
const extractionPrompt = buildExtractionPrompt(transcript, existing);
|
|
44
|
+
const promptMs = Date.now() - t;
|
|
45
|
+
t = Date.now();
|
|
41
46
|
const response = await this.options.callLLM("You are a memory extraction assistant. Extract only durable, reusable information worth carrying into future sessions.", extractionPrompt);
|
|
47
|
+
const llmMs = Date.now() - t;
|
|
48
|
+
t = Date.now();
|
|
42
49
|
const entries = parseExtractionResponse(response);
|
|
50
|
+
const parseMs = Date.now() - t;
|
|
51
|
+
t = Date.now();
|
|
43
52
|
for (const entry of entries) {
|
|
44
53
|
mm.save({
|
|
45
54
|
type: entry.type,
|
|
@@ -48,11 +57,20 @@ export class MemoryOrchestrator {
|
|
|
48
57
|
content: entry.content,
|
|
49
58
|
});
|
|
50
59
|
}
|
|
60
|
+
const saveMs = Date.now() - t;
|
|
51
61
|
extracted = entries.length;
|
|
52
62
|
logger.info("memory.extraction_done", {
|
|
53
63
|
sessionId,
|
|
54
64
|
extracted,
|
|
55
65
|
elapsedMs: Date.now() - startTime,
|
|
66
|
+
loadMs,
|
|
67
|
+
promptMs,
|
|
68
|
+
llmMs,
|
|
69
|
+
parseMs,
|
|
70
|
+
saveMs,
|
|
71
|
+
existingCount: existing.length,
|
|
72
|
+
transcriptMessages: transcript.length,
|
|
73
|
+
responseChars: response.length,
|
|
56
74
|
});
|
|
57
75
|
}
|
|
58
76
|
catch (err) {
|
|
@@ -15,6 +15,14 @@ export interface NotificationOptions {
|
|
|
15
15
|
* Send a desktop notification.
|
|
16
16
|
*/
|
|
17
17
|
export declare function notify(options: NotificationOptions): void;
|
|
18
|
+
/** Escape a string for embedding inside an AppleScript double-quoted literal. */
|
|
19
|
+
export declare function escapeAppleScriptString(str: string): string;
|
|
20
|
+
/** Build the osascript argv (a single `-e <script>` pair). */
|
|
21
|
+
export declare function buildOsascriptArgs(title: string, message: string, sound: boolean): string[];
|
|
22
|
+
/** Build the notify-send argv with title/message as separate tokens. */
|
|
23
|
+
export declare function buildNotifySendArgs(title: string, message: string, urgency: "low" | "normal" | "critical"): string[];
|
|
24
|
+
/** Build the powershell.exe argv (a single `-Command <script>` element). */
|
|
25
|
+
export declare function buildPowershellArgs(title: string, message: string): string[];
|
|
18
26
|
/**
|
|
19
27
|
* Send a notification that a task/agent has completed.
|
|
20
28
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Sends notifications when tasks complete, agents finish, or errors occur.
|
|
5
5
|
* Falls back gracefully when no notification system is available.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
8
|
/**
|
|
9
9
|
* Send a desktop notification.
|
|
10
10
|
*/
|
|
@@ -12,37 +12,54 @@ export function notify(options) {
|
|
|
12
12
|
const { title, message, sound = false, urgency = "normal" } = options;
|
|
13
13
|
try {
|
|
14
14
|
if (process.platform === "darwin") {
|
|
15
|
-
// macOS: osascript
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// macOS: osascript. Pass the script as a single -e argv element via
|
|
16
|
+
// execFileSync (no shell), so title/message are never seen by the shell.
|
|
17
|
+
execFileSync("osascript", buildOsascriptArgs(title, message, sound), { timeout: 5000 });
|
|
18
18
|
}
|
|
19
19
|
else if (process.platform === "linux") {
|
|
20
|
-
// Linux: notify-send
|
|
21
|
-
|
|
22
|
-
execSync(`notify-send ${urgencyFlag} "${escape(title)}" "${escape(message)}"`, {
|
|
23
|
-
timeout: 5000,
|
|
24
|
-
});
|
|
20
|
+
// Linux: notify-send. argv keeps title/message as separate tokens.
|
|
21
|
+
execFileSync("notify-send", buildNotifySendArgs(title, message, urgency), { timeout: 5000 });
|
|
25
22
|
}
|
|
26
23
|
else if (process.platform === "win32") {
|
|
27
|
-
// Windows: PowerShell toast
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
$text = $xml.GetElementsByTagName('text')
|
|
32
|
-
$text[0].AppendChild($xml.CreateTextNode('${escape(title)}')) | Out-Null
|
|
33
|
-
$text[1].AppendChild($xml.CreateTextNode('${escape(message)}')) | Out-Null
|
|
34
|
-
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
|
|
35
|
-
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('CodeShell').Show($toast)
|
|
36
|
-
`.trim();
|
|
37
|
-
execSync(`powershell.exe -NoProfile -Command "${ps.replace(/\n/g, "; ")}"`, {
|
|
38
|
-
timeout: 5000,
|
|
39
|
-
});
|
|
24
|
+
// Windows: PowerShell toast. The script is one -Command argv element
|
|
25
|
+
// (no outer shell); title/message are escaped for PowerShell single
|
|
26
|
+
// quotes (' → '').
|
|
27
|
+
execFileSync("powershell.exe", buildPowershellArgs(title, message), { timeout: 5000 });
|
|
40
28
|
}
|
|
41
29
|
}
|
|
42
30
|
catch {
|
|
43
31
|
// Silently fail — notifications are best-effort
|
|
44
32
|
}
|
|
45
33
|
}
|
|
34
|
+
/** Escape a string for embedding inside an AppleScript double-quoted literal. */
|
|
35
|
+
export function escapeAppleScriptString(str) {
|
|
36
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, " ");
|
|
37
|
+
}
|
|
38
|
+
/** Build the osascript argv (a single `-e <script>` pair). */
|
|
39
|
+
export function buildOsascriptArgs(title, message, sound) {
|
|
40
|
+
const soundClause = sound ? ' sound name "default"' : "";
|
|
41
|
+
const script = `display notification "${escapeAppleScriptString(message)}"` +
|
|
42
|
+
` with title "${escapeAppleScriptString(title)}"${soundClause}`;
|
|
43
|
+
return ["-e", script];
|
|
44
|
+
}
|
|
45
|
+
/** Build the notify-send argv with title/message as separate tokens. */
|
|
46
|
+
export function buildNotifySendArgs(title, message, urgency) {
|
|
47
|
+
return ["-u", urgency, title, message];
|
|
48
|
+
}
|
|
49
|
+
/** Build the powershell.exe argv (a single `-Command <script>` element). */
|
|
50
|
+
export function buildPowershellArgs(title, message) {
|
|
51
|
+
const esc = (s) => s.replace(/'/g, "''").replace(/\n/g, " ");
|
|
52
|
+
const ps = [
|
|
53
|
+
"[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
|
|
54
|
+
"$xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)",
|
|
55
|
+
"$text = $xml.GetElementsByTagName('text')",
|
|
56
|
+
`$text[0].AppendChild($xml.CreateTextNode('${esc(title)}')) | Out-Null`,
|
|
57
|
+
`$text[1].AppendChild($xml.CreateTextNode('${esc(message)}')) | Out-Null`,
|
|
58
|
+
"$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)",
|
|
59
|
+
"[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('CodeShell').Show($toast)",
|
|
60
|
+
].join("; ");
|
|
61
|
+
return ["-NoProfile", "-Command", ps];
|
|
62
|
+
}
|
|
46
63
|
/**
|
|
47
64
|
* Send a notification that a task/agent has completed.
|
|
48
65
|
*/
|
|
@@ -64,6 +81,3 @@ export function notifyError(context, error) {
|
|
|
64
81
|
urgency: "critical",
|
|
65
82
|
});
|
|
66
83
|
}
|
|
67
|
-
function escape(str) {
|
|
68
|
-
return str.replace(/['"\\]/g, "\\$&").replace(/\n/g, " ");
|
|
69
|
-
}
|
package/dist/services/oauth.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
import { randomBytes, createHash } from "node:crypto";
|
|
8
|
-
import {
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { browserOpenCommand } from "./browser-open.js";
|
|
9
10
|
/**
|
|
10
11
|
* Generate PKCE code verifier and challenge.
|
|
11
12
|
*/
|
|
@@ -18,12 +19,9 @@ function generatePKCE() {
|
|
|
18
19
|
* Open a URL in the system browser.
|
|
19
20
|
*/
|
|
20
21
|
function openBrowser(url) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
? `start "${url}"`
|
|
25
|
-
: `xdg-open "${url}"`;
|
|
26
|
-
exec(cmd, () => { });
|
|
22
|
+
// execFile with an argv array — no shell, so the URL can't be interpreted.
|
|
23
|
+
const { cmd, args } = browserOpenCommand(process.platform, url);
|
|
24
|
+
execFile(cmd, args, () => { });
|
|
27
25
|
}
|
|
28
26
|
/**
|
|
29
27
|
* Run the OAuth authorization code flow.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SessionMemoryEntry } from "./session-memory.js";
|
|
2
|
+
/**
|
|
3
|
+
* Order session memories most-recent-first by their `createdAt` field. The
|
|
4
|
+
* caller previously sorted by filename (= sessionId), which has no
|
|
5
|
+
* chronological meaning (review-2026-05-30). Returns a new array; input is not
|
|
6
|
+
* mutated.
|
|
7
|
+
*/
|
|
8
|
+
export declare function sortSessionMemoriesByRecency(entries: SessionMemoryEntry[]): SessionMemoryEntry[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order session memories most-recent-first by their `createdAt` field. The
|
|
3
|
+
* caller previously sorted by filename (= sessionId), which has no
|
|
4
|
+
* chronological meaning (review-2026-05-30). Returns a new array; input is not
|
|
5
|
+
* mutated.
|
|
6
|
+
*/
|
|
7
|
+
export function sortSessionMemoriesByRecency(entries) {
|
|
8
|
+
return [...entries].sort((a, b) => (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0));
|
|
9
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
|
+
import { sortSessionMemoriesByRecency } from "./session-memory-sort.js";
|
|
10
11
|
const MEMORY_DIR = join(homedir(), ".code-shell", "session-memories");
|
|
11
12
|
/**
|
|
12
13
|
* Save a session memory entry.
|
|
@@ -36,20 +37,18 @@ export function loadSessionMemory(sessionId) {
|
|
|
36
37
|
export function listSessionMemories(limit = 50) {
|
|
37
38
|
if (!existsSync(MEMORY_DIR))
|
|
38
39
|
return [];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
.sort()
|
|
42
|
-
.reverse()
|
|
43
|
-
.slice(0, limit);
|
|
40
|
+
// Read all entries, then order by createdAt (not by filename — the filename
|
|
41
|
+
// is the sessionId, which has no chronological meaning), then take `limit`.
|
|
44
42
|
const entries = [];
|
|
45
|
-
for (const file of
|
|
43
|
+
for (const file of readdirSync(MEMORY_DIR)) {
|
|
44
|
+
if (!file.endsWith(".json"))
|
|
45
|
+
continue;
|
|
46
46
|
try {
|
|
47
|
-
|
|
48
|
-
entries.push(entry);
|
|
47
|
+
entries.push(JSON.parse(readFileSync(join(MEMORY_DIR, file), "utf-8")));
|
|
49
48
|
}
|
|
50
49
|
catch { }
|
|
51
50
|
}
|
|
52
|
-
return entries;
|
|
51
|
+
return sortSessionMemoriesByRecency(entries).slice(0, limit);
|
|
53
52
|
}
|
|
54
53
|
/**
|
|
55
54
|
* Search session memories by keyword.
|
|
@@ -29,10 +29,17 @@ export class FileHistory {
|
|
|
29
29
|
const existing = this.snapshots.find((s) => s.filePath === absPath && s.hash === hash);
|
|
30
30
|
if (existing)
|
|
31
31
|
return existing;
|
|
32
|
-
// Create backup
|
|
32
|
+
// Create backup. The filename must uniquely identify (path, content): a
|
|
33
|
+
// bare 100-char tail of the path can collide for two different files
|
|
34
|
+
// whose tails coincide, and with the same ms timestamp the second
|
|
35
|
+
// copyFileSync would silently overwrite the first backup while both
|
|
36
|
+
// index entries point to it (restore would then return the wrong file's
|
|
37
|
+
// content). Fold a full-path hash and the content hash into the name so
|
|
38
|
+
// distinct (path, content) pairs never share a backup file.
|
|
33
39
|
const timestamp = Date.now();
|
|
34
|
-
const
|
|
35
|
-
const
|
|
40
|
+
const pathHash = createHash("md5").update(absPath).digest("hex").slice(0, 8);
|
|
41
|
+
const safeName = absPath.replace(/[/\\:]/g, "_").slice(-80);
|
|
42
|
+
const backupPath = join(this.historyDir, `${timestamp}_${pathHash}_${hash.slice(0, 8)}_${safeName}`);
|
|
36
43
|
copyFileSync(absPath, backupPath);
|
|
37
44
|
const snapshot = {
|
|
38
45
|
filePath: absPath,
|