open-multi-agent-kit 0.97.0 → 0.98.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -1
- package/README.md +41 -9
- package/dist/cli/args.d.ts +2 -3
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +13 -189
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.d.ts +3 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +194 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/initial-message.d.ts.map +1 -1
- package/dist/cli/initial-message.js +2 -1
- package/dist/cli/initial-message.js.map +1 -1
- package/dist/cli/resource-paths.d.ts +4 -0
- package/dist/cli/resource-paths.d.ts.map +1 -0
- package/dist/cli/resource-paths.js +59 -0
- package/dist/cli/resource-paths.js.map +1 -0
- package/dist/commands/adaptorch-doctor-cli.d.ts +13 -0
- package/dist/commands/adaptorch-doctor-cli.d.ts.map +1 -0
- package/dist/commands/adaptorch-doctor-cli.js +115 -0
- package/dist/commands/adaptorch-doctor-cli.js.map +1 -0
- package/dist/commands/resource-doctor-cli.d.ts +4 -0
- package/dist/commands/resource-doctor-cli.d.ts.map +1 -1
- package/dist/commands/resource-doctor-cli.js +35 -5
- package/dist/commands/resource-doctor-cli.js.map +1 -1
- package/dist/core/adaptorch-bridge.d.ts +11 -13
- package/dist/core/adaptorch-bridge.d.ts.map +1 -1
- package/dist/core/adaptorch-bridge.js +11 -13
- package/dist/core/adaptorch-bridge.js.map +1 -1
- package/dist/core/agent-session-snapshot.d.ts +16 -0
- package/dist/core/agent-session-snapshot.d.ts.map +1 -0
- package/dist/core/agent-session-snapshot.js +111 -0
- package/dist/core/agent-session-snapshot.js.map +1 -0
- package/dist/core/agent-session.d.ts +5 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +27 -219
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-tool-settings.d.ts +10 -0
- package/dist/core/agent-tool-settings.d.ts.map +1 -1
- package/dist/core/agent-tool-settings.js +10 -1
- package/dist/core/agent-tool-settings.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +7 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +25 -61
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/index.d.ts +1 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +1 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/knowledge-triage-format.d.ts +9 -0
- package/dist/core/compaction/knowledge-triage-format.d.ts.map +1 -0
- package/dist/core/compaction/knowledge-triage-format.js +71 -0
- package/dist/core/compaction/knowledge-triage-format.js.map +1 -0
- package/dist/core/compaction/knowledge-triage-types.d.ts +7 -0
- package/dist/core/compaction/knowledge-triage-types.d.ts.map +1 -0
- package/dist/core/compaction/knowledge-triage-types.js +2 -0
- package/dist/core/compaction/knowledge-triage-types.js.map +1 -0
- package/dist/core/compaction/knowledge-triage.d.ts +29 -0
- package/dist/core/compaction/knowledge-triage.d.ts.map +1 -0
- package/dist/core/compaction/knowledge-triage.js +189 -0
- package/dist/core/compaction/knowledge-triage.js.map +1 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -1
- package/dist/core/compaction/transaction.js +1 -1
- package/dist/core/compaction/transaction.js.map +1 -1
- package/dist/core/compaction/utils.d.ts +4 -0
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +37 -0
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/completion-sound-io.d.ts +14 -0
- package/dist/core/completion-sound-io.d.ts.map +1 -0
- package/dist/core/completion-sound-io.js +64 -0
- package/dist/core/completion-sound-io.js.map +1 -0
- package/dist/core/completion-sound.d.ts +4 -10
- package/dist/core/completion-sound.d.ts.map +1 -1
- package/dist/core/completion-sound.js +28 -58
- package/dist/core/completion-sound.js.map +1 -1
- package/dist/core/context-budget-relevance.d.ts +15 -2
- package/dist/core/context-budget-relevance.d.ts.map +1 -1
- package/dist/core/context-budget-relevance.js +21 -9
- package/dist/core/context-budget-relevance.js.map +1 -1
- package/dist/core/context-budget-system-prompt-items.d.ts +1 -1
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/context-budget-system-prompt.d.ts +1 -1
- package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt.js.map +1 -1
- package/dist/core/context-file.d.ts +23 -0
- package/dist/core/context-file.d.ts.map +1 -0
- package/dist/core/context-file.js +17 -0
- package/dist/core/context-file.js.map +1 -0
- package/dist/core/keybindings.d.ts +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +6 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-prompt-policy.d.ts +1 -1
- package/dist/core/model-prompt-policy.d.ts.map +1 -1
- package/dist/core/model-prompt-policy.js.map +1 -1
- package/dist/core/prompt-settlement.d.ts +6 -3
- package/dist/core/prompt-settlement.d.ts.map +1 -1
- package/dist/core/prompt-settlement.js +10 -14
- package/dist/core/prompt-settlement.js.map +1 -1
- package/dist/core/reasoning-router-promotion.d.ts +41 -1
- package/dist/core/reasoning-router-promotion.d.ts.map +1 -1
- package/dist/core/reasoning-router-promotion.js +31 -2
- package/dist/core/reasoning-router-promotion.js.map +1 -1
- package/dist/core/reasoning-router-replay-stability.d.ts +65 -0
- package/dist/core/reasoning-router-replay-stability.d.ts.map +1 -0
- package/dist/core/reasoning-router-replay-stability.js +67 -0
- package/dist/core/reasoning-router-replay-stability.js.map +1 -0
- package/dist/core/reasoning-router-v4-weights.d.ts +6 -6
- package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4-weights.js +3 -3
- package/dist/core/reasoning-router-v4-weights.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js +6 -6
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/redaction.d.ts.map +1 -1
- package/dist/core/redaction.js +1 -1
- package/dist/core/redaction.js.map +1 -1
- package/dist/core/resource-admission.d.ts +2 -1
- package/dist/core/resource-admission.d.ts.map +1 -1
- package/dist/core/resource-admission.js +11 -0
- package/dist/core/resource-admission.js.map +1 -1
- package/dist/core/resource-governor-format.d.ts +1 -1
- package/dist/core/resource-governor-format.d.ts.map +1 -1
- package/dist/core/resource-governor-format.js.map +1 -1
- package/dist/core/resource-governor-settings.d.ts +6 -12
- package/dist/core/resource-governor-settings.d.ts.map +1 -1
- package/dist/core/resource-governor-settings.js.map +1 -1
- package/dist/core/resource-governor-types.d.ts +24 -0
- package/dist/core/resource-governor-types.d.ts.map +1 -0
- package/dist/core/resource-governor-types.js +15 -0
- package/dist/core/resource-governor-types.js.map +1 -0
- package/dist/core/resource-loader.d.ts +1 -7
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/resource-observation-journal.d.ts +2 -1
- package/dist/core/resource-observation-journal.d.ts.map +1 -1
- package/dist/core/resource-observation-journal.js +4 -3
- package/dist/core/resource-observation-journal.js.map +1 -1
- package/dist/core/resource-observation-report-reader.d.ts +7 -0
- package/dist/core/resource-observation-report-reader.d.ts.map +1 -0
- package/dist/core/resource-observation-report-reader.js +157 -0
- package/dist/core/resource-observation-report-reader.js.map +1 -0
- package/dist/core/resource-observation-report.d.ts +24 -0
- package/dist/core/resource-observation-report.d.ts.map +1 -0
- package/dist/core/resource-observation-report.js +101 -0
- package/dist/core/resource-observation-report.js.map +1 -0
- package/dist/core/router-feedback-collector.d.ts +1 -1
- package/dist/core/router-feedback-collector.d.ts.map +1 -1
- package/dist/core/router-feedback-collector.js.map +1 -1
- package/dist/core/session-failure-cause.d.ts +33 -0
- package/dist/core/session-failure-cause.d.ts.map +1 -0
- package/dist/core/session-failure-cause.js +141 -0
- package/dist/core/session-failure-cause.js.map +1 -0
- package/dist/core/session-system-prompt.d.ts +1 -1
- package/dist/core/session-system-prompt.d.ts.map +1 -1
- package/dist/core/session-system-prompt.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +7 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/workload-classifier.d.ts +2 -10
- package/dist/core/workload-classifier.d.ts.map +1 -1
- package/dist/core/workload-classifier.js +17 -0
- package/dist/core/workload-classifier.js.map +1 -1
- package/dist/core/workload-family-matcher.d.ts +1 -1
- package/dist/core/workload-family-matcher.d.ts.map +1 -1
- package/dist/core/workload-family-matcher.js.map +1 -1
- package/dist/core/workload-shard-execution-types.d.ts +40 -0
- package/dist/core/workload-shard-execution-types.d.ts.map +1 -0
- package/dist/core/workload-shard-execution-types.js +14 -0
- package/dist/core/workload-shard-execution-types.js.map +1 -0
- package/dist/core/workload-shard-executor.d.ts +3 -27
- package/dist/core/workload-shard-executor.d.ts.map +1 -1
- package/dist/core/workload-shard-executor.js.map +1 -1
- package/dist/core/workload-shard-runner.d.ts +1 -1
- package/dist/core/workload-shard-runner.d.ts.map +1 -1
- package/dist/core/workload-shard-runner.js.map +1 -1
- package/dist/core/workload-types.d.ts +24 -0
- package/dist/core/workload-types.d.ts.map +1 -0
- package/dist/core/workload-types.js +15 -0
- package/dist/core/workload-types.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +27 -47
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/theme/omk-control-panel.json +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +42 -33
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +14 -2
- package/dist/utils/clipboard.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +20 -18
- package/docs/adaptorch-preview.md +9 -9
- package/docs/compaction.md +24 -3
- package/docs/correctness-wall.md +10 -10
- package/docs/docs.json +12 -0
- package/docs/environment-variables.md +9 -2
- package/docs/index.md +3 -0
- package/docs/runtime-algorithms.md +359 -0
- package/docs/sdk.md +2 -2
- package/docs/settings.md +11 -10
- package/docs/usage.md +18 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +6 -6
- package/dist/core/context-budget-governor.d.ts +0 -67
- package/dist/core/context-budget-governor.d.ts.map +0 -1
- package/dist/core/context-budget-governor.js +0 -224
- package/dist/core/context-budget-governor.js.map +0 -1
- package/dist/core/lean-ctx.d.ts +0 -99
- package/dist/core/lean-ctx.d.ts.map +0 -1
- package/dist/core/lean-ctx.js +0 -225
- package/dist/core/lean-ctx.js.map +0 -1
- package/dist/core/read-anchors.d.ts +0 -110
- package/dist/core/read-anchors.d.ts.map +0 -1
- package/dist/core/read-anchors.js +0 -193
- package/dist/core/read-anchors.js.map +0 -1
- package/dist/core/recovery-checkpoint.d.ts +0 -196
- package/dist/core/recovery-checkpoint.d.ts.map +0 -1
- package/dist/core/recovery-checkpoint.js +0 -382
- package/dist/core/recovery-checkpoint.js.map +0 -1
- package/dist/core/sandbox-policy-v2.d.ts +0 -49
- package/dist/core/sandbox-policy-v2.d.ts.map +0 -1
- package/dist/core/sandbox-policy-v2.js +0 -368
- package/dist/core/sandbox-policy-v2.js.map +0 -1
- package/dist/guardrails/audit-logger.d.ts +0 -4
- package/dist/guardrails/audit-logger.d.ts.map +0 -1
- package/dist/guardrails/audit-logger.js +0 -28
- package/dist/guardrails/audit-logger.js.map +0 -1
- package/dist/guardrails/freedomd-adapter.d.ts +0 -16
- package/dist/guardrails/freedomd-adapter.d.ts.map +0 -1
- package/dist/guardrails/freedomd-adapter.js +0 -87
- package/dist/guardrails/freedomd-adapter.js.map +0 -1
- package/dist/orchestration/lane-grant-auditor.d.ts +0 -4
- package/dist/orchestration/lane-grant-auditor.d.ts.map +0 -1
- package/dist/orchestration/lane-grant-auditor.js +0 -87
- package/dist/orchestration/lane-grant-auditor.js.map +0 -1
- package/dist/types/guardrails.d.ts +0 -61
- package/dist/types/guardrails.d.ts.map +0 -1
- package/dist/types/guardrails.js +0 -9
- package/dist/types/guardrails.js.map +0 -1
- package/dist/types/lane-grant.d.ts +0 -58
- package/dist/types/lane-grant.d.ts.map +0 -1
- package/dist/types/lane-grant.js +0 -9
- package/dist/types/lane-grant.js.map +0 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shard execution contract.
|
|
3
|
+
*
|
|
4
|
+
* These declarations describe the call the executor makes and the input it
|
|
5
|
+
* takes. They lived in the executor, so the runner — which the executor calls
|
|
6
|
+
* to advance one shard — had to import the executor back to name its own
|
|
7
|
+
* parameter type. Moving the contract below both leaves the executor free to
|
|
8
|
+
* depend on the runner without the runner depending on the executor.
|
|
9
|
+
*
|
|
10
|
+
* Types only. Imports stay on the plan, store, admission, and permit modules,
|
|
11
|
+
* none of which reach the executor or the runner.
|
|
12
|
+
*/
|
|
13
|
+
import type { ResourceAdmissionDecision } from "./resource-admission.ts";
|
|
14
|
+
import type { WorkloadPermitPool } from "./workload-permit-pool.ts";
|
|
15
|
+
import type { WorkloadShardPlan, WorkloadShardSpec } from "./workload-shard-plan.ts";
|
|
16
|
+
import type { WorkloadShardStore } from "./workload-shard-store.ts";
|
|
17
|
+
export interface ShardRunContext {
|
|
18
|
+
readonly shard: WorkloadShardSpec;
|
|
19
|
+
readonly attempt: number;
|
|
20
|
+
readonly signal?: AbortSignal;
|
|
21
|
+
}
|
|
22
|
+
export type ShardRunner = (context: ShardRunContext) => Promise<{
|
|
23
|
+
readonly exitCode: number;
|
|
24
|
+
readonly evidenceRefs?: readonly string[];
|
|
25
|
+
}>;
|
|
26
|
+
export interface ExecuteWorkloadShardPlanInput {
|
|
27
|
+
readonly plan: WorkloadShardPlan;
|
|
28
|
+
readonly store: WorkloadShardStore;
|
|
29
|
+
readonly runner: ShardRunner;
|
|
30
|
+
readonly decision: ResourceAdmissionDecision;
|
|
31
|
+
readonly permitPool?: WorkloadPermitPool;
|
|
32
|
+
readonly signal?: AbortSignal;
|
|
33
|
+
/** §13.5 step 4 retry policy: re-arm previously failed shards. Default false. */
|
|
34
|
+
readonly retryFailed?: boolean;
|
|
35
|
+
/** §13.4: aborted -> pending needs an explicit resume; this call is that act. Default true. */
|
|
36
|
+
readonly resumeAborted?: boolean;
|
|
37
|
+
readonly permitWaitTimeoutMs?: number;
|
|
38
|
+
readonly now?: () => Date;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=workload-shard-execution-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload-shard-execution-types.d.ts","sourceRoot":"","sources":["../../src/core/workload-shard-execution-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,WAAW,GAAG,CACzB,OAAO,EAAE,eAAe,KACpB,OAAO,CAAC;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AAEvF,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,yBAAyB,CAAC;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,kFAAiF;IACjF,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,gGAA+F;IAC/F,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B","sourcesContent":["/**\n * Shard execution contract.\n *\n * These declarations describe the call the executor makes and the input it\n * takes. They lived in the executor, so the runner — which the executor calls\n * to advance one shard — had to import the executor back to name its own\n * parameter type. Moving the contract below both leaves the executor free to\n * depend on the runner without the runner depending on the executor.\n *\n * Types only. Imports stay on the plan, store, admission, and permit modules,\n * none of which reach the executor or the runner.\n */\n\nimport type { ResourceAdmissionDecision } from \"./resource-admission.ts\";\nimport type { WorkloadPermitPool } from \"./workload-permit-pool.ts\";\nimport type { WorkloadShardPlan, WorkloadShardSpec } from \"./workload-shard-plan.ts\";\nimport type { WorkloadShardStore } from \"./workload-shard-store.ts\";\n\nexport interface ShardRunContext {\n\treadonly shard: WorkloadShardSpec;\n\treadonly attempt: number;\n\treadonly signal?: AbortSignal;\n}\n\nexport type ShardRunner = (\n\tcontext: ShardRunContext,\n) => Promise<{ readonly exitCode: number; readonly evidenceRefs?: readonly string[] }>;\n\nexport interface ExecuteWorkloadShardPlanInput {\n\treadonly plan: WorkloadShardPlan;\n\treadonly store: WorkloadShardStore;\n\treadonly runner: ShardRunner;\n\treadonly decision: ResourceAdmissionDecision;\n\treadonly permitPool?: WorkloadPermitPool;\n\treadonly signal?: AbortSignal;\n\t/** §13.5 step 4 retry policy: re-arm previously failed shards. Default false. */\n\treadonly retryFailed?: boolean;\n\t/** §13.4: aborted -> pending needs an explicit resume; this call is that act. Default true. */\n\treadonly resumeAborted?: boolean;\n\treadonly permitWaitTimeoutMs?: number;\n\treadonly now?: () => Date;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shard execution contract.
|
|
3
|
+
*
|
|
4
|
+
* These declarations describe the call the executor makes and the input it
|
|
5
|
+
* takes. They lived in the executor, so the runner — which the executor calls
|
|
6
|
+
* to advance one shard — had to import the executor back to name its own
|
|
7
|
+
* parameter type. Moving the contract below both leaves the executor free to
|
|
8
|
+
* depend on the runner without the runner depending on the executor.
|
|
9
|
+
*
|
|
10
|
+
* Types only. Imports stay on the plan, store, admission, and permit modules,
|
|
11
|
+
* none of which reach the executor or the runner.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=workload-shard-execution-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload-shard-execution-types.js","sourceRoot":"","sources":["../../src/core/workload-shard-execution-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG","sourcesContent":["/**\n * Shard execution contract.\n *\n * These declarations describe the call the executor makes and the input it\n * takes. They lived in the executor, so the runner — which the executor calls\n * to advance one shard — had to import the executor back to name its own\n * parameter type. Moving the contract below both leaves the executor free to\n * depend on the runner without the runner depending on the executor.\n *\n * Types only. Imports stay on the plan, store, admission, and permit modules,\n * none of which reach the executor or the runner.\n */\n\nimport type { ResourceAdmissionDecision } from \"./resource-admission.ts\";\nimport type { WorkloadPermitPool } from \"./workload-permit-pool.ts\";\nimport type { WorkloadShardPlan, WorkloadShardSpec } from \"./workload-shard-plan.ts\";\nimport type { WorkloadShardStore } from \"./workload-shard-store.ts\";\n\nexport interface ShardRunContext {\n\treadonly shard: WorkloadShardSpec;\n\treadonly attempt: number;\n\treadonly signal?: AbortSignal;\n}\n\nexport type ShardRunner = (\n\tcontext: ShardRunContext,\n) => Promise<{ readonly exitCode: number; readonly evidenceRefs?: readonly string[] }>;\n\nexport interface ExecuteWorkloadShardPlanInput {\n\treadonly plan: WorkloadShardPlan;\n\treadonly store: WorkloadShardStore;\n\treadonly runner: ShardRunner;\n\treadonly decision: ResourceAdmissionDecision;\n\treadonly permitPool?: WorkloadPermitPool;\n\treadonly signal?: AbortSignal;\n\t/** §13.5 step 4 retry policy: re-arm previously failed shards. Default false. */\n\treadonly retryFailed?: boolean;\n\t/** §13.4: aborted -> pending needs an explicit resume; this call is that act. Default true. */\n\treadonly resumeAborted?: boolean;\n\treadonly permitWaitTimeoutMs?: number;\n\treadonly now?: () => Date;\n}\n"]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type
|
|
3
|
-
|
|
4
|
-
import { type WorkloadShardStore } from "./workload-shard-store.ts";
|
|
1
|
+
import type { ExecuteWorkloadShardPlanInput, ShardRunContext, ShardRunner } from "./workload-shard-execution-types.ts";
|
|
2
|
+
import { type WorkloadShardState } from "./workload-shard-plan.ts";
|
|
3
|
+
export type { ExecuteWorkloadShardPlanInput, ShardRunContext, ShardRunner };
|
|
5
4
|
/**
|
|
6
5
|
* Workload shard executor (OMK v0.97.x roadmap §13, M5 completion slice).
|
|
7
6
|
*
|
|
@@ -19,29 +18,6 @@ import { type WorkloadShardStore } from "./workload-shard-store.ts";
|
|
|
19
18
|
* it only runs what the plan's descriptors already describe via the
|
|
20
19
|
* injected runner.
|
|
21
20
|
*/
|
|
22
|
-
export interface ShardRunContext {
|
|
23
|
-
readonly shard: WorkloadShardSpec;
|
|
24
|
-
readonly attempt: number;
|
|
25
|
-
readonly signal?: AbortSignal;
|
|
26
|
-
}
|
|
27
|
-
export type ShardRunner = (context: ShardRunContext) => Promise<{
|
|
28
|
-
readonly exitCode: number;
|
|
29
|
-
readonly evidenceRefs?: readonly string[];
|
|
30
|
-
}>;
|
|
31
|
-
export interface ExecuteWorkloadShardPlanInput {
|
|
32
|
-
readonly plan: WorkloadShardPlan;
|
|
33
|
-
readonly store: WorkloadShardStore;
|
|
34
|
-
readonly runner: ShardRunner;
|
|
35
|
-
readonly decision: ResourceAdmissionDecision;
|
|
36
|
-
readonly permitPool?: WorkloadPermitPool;
|
|
37
|
-
readonly signal?: AbortSignal;
|
|
38
|
-
/** §13.5 step 4 retry policy: re-arm previously failed shards. Default false. */
|
|
39
|
-
readonly retryFailed?: boolean;
|
|
40
|
-
/** §13.4: aborted -> pending needs an explicit resume; this call is that act. Default true. */
|
|
41
|
-
readonly resumeAborted?: boolean;
|
|
42
|
-
readonly permitWaitTimeoutMs?: number;
|
|
43
|
-
readonly now?: () => Date;
|
|
44
|
-
}
|
|
45
21
|
export interface ShardResultSummary {
|
|
46
22
|
readonly shardId: string;
|
|
47
23
|
readonly state: WorkloadShardState;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workload-shard-executor.d.ts","sourceRoot":"","sources":["../../src/core/workload-shard-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAIN,KAAK,iBAAiB,EAEtB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/F;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,WAAW,GAAG,CACzB,OAAO,EAAE,eAAe,KACpB,OAAO,CAAC;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AAEvF,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,yBAAyB,CAAC;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,kFAAiF;IACjF,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,gGAA+F;IAC/F,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,kEAAgE;AAChE,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,IAAI,EAAE,0BAA0B,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9D,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,6BAA6B,GAClC,OAAO,CAAC,8BAA8B,CAAC,CAoFzC","sourcesContent":["import type { ResourceAdmissionDecision } from \"./resource-admission.ts\";\nimport type { WorkloadPermitPool } from \"./workload-permit-pool.ts\";\nimport {\n\tplanShardResume,\n\treduceShardRecords,\n\tvalidateWorkloadShardPlan,\n\ttype WorkloadShardPlan,\n\ttype WorkloadShardProjection,\n\ttype WorkloadShardSpec,\n\ttype WorkloadShardState,\n} from \"./workload-shard-plan.ts\";\nimport { applyTransition, runOneShard, shardById } from \"./workload-shard-runner.ts\";\nimport { WorkloadShardJournalError, type WorkloadShardStore } from \"./workload-shard-store.ts\";\n\n/**\n * Workload shard executor (OMK v0.97.x roadmap §13, M5 completion slice).\n *\n * Drives a validated `WorkloadShardPlan` through the journalled §13.4 state\n * machine: passed shards are terminal and never re-run (§13.5 step 3),\n * crash-orphaned `running` shards are recovered through `pending` re-arm\n * records, concurrency is recomputed from the CURRENT admission decision\n * (§13.5 step 6) plus the shared permit pool, and completion produces an\n * aggregate evidence receipt (§13.6) — an observation input for\n * `evaluateTask()`, never a task verdict by itself.\n *\n * Fail-closed rules (§21): a corrupt journal is quarantined and the run is\n * `blocked`; a journal written for a different plan is `blocked`; an invalid\n * plan never starts. The executor never rewrites shard commands (§12.3) —\n * it only runs what the plan's descriptors already describe via the\n * injected runner.\n */\n\nexport interface ShardRunContext {\n\treadonly shard: WorkloadShardSpec;\n\treadonly attempt: number;\n\treadonly signal?: AbortSignal;\n}\n\nexport type ShardRunner = (\n\tcontext: ShardRunContext,\n) => Promise<{ readonly exitCode: number; readonly evidenceRefs?: readonly string[] }>;\n\nexport interface ExecuteWorkloadShardPlanInput {\n\treadonly plan: WorkloadShardPlan;\n\treadonly store: WorkloadShardStore;\n\treadonly runner: ShardRunner;\n\treadonly decision: ResourceAdmissionDecision;\n\treadonly permitPool?: WorkloadPermitPool;\n\treadonly signal?: AbortSignal;\n\t/** §13.5 step 4 retry policy: re-arm previously failed shards. Default false. */\n\treadonly retryFailed?: boolean;\n\t/** §13.4: aborted -> pending needs an explicit resume; this call is that act. Default true. */\n\treadonly resumeAborted?: boolean;\n\treadonly permitWaitTimeoutMs?: number;\n\treadonly now?: () => Date;\n}\n\nexport interface ShardResultSummary {\n\treadonly shardId: string;\n\treadonly state: WorkloadShardState;\n\treadonly attempt: number;\n\treadonly evidenceRefs: readonly string[];\n\treadonly reasonCode?: string;\n}\n\n/** §13.6 aggregate evidence receipt; §15.1 observation kind. */\nexport interface WorkloadShardAggregateEvidence {\n\treadonly kind: \"workload_shard_result.v1\";\n\treadonly planId: string;\n\treadonly promptRunId: string;\n\treadonly commandDigest: string;\n\treadonly strategy: string;\n\treadonly verdict: \"passed\" | \"failed\" | \"aborted\" | \"blocked\";\n\treadonly reasonCodes: readonly string[];\n\treadonly shardCount: number;\n\treadonly counts: Readonly<Record<WorkloadShardState, number>>;\n\treadonly shards: readonly ShardResultSummary[];\n\treadonly effectiveConcurrency: number;\n\treadonly completedAt: string;\n}\n\nexport async function executeWorkloadShardPlan(\n\tinput: ExecuteWorkloadShardPlanInput,\n): Promise<WorkloadShardAggregateEvidence> {\n\tconst now = input.now ?? (() => new Date());\n\tconst planErrors = validateWorkloadShardPlan(input.plan);\n\tif (planErrors.length > 0) {\n\t\treturn blockedEvidence(input, now, [\"plan.invalid\", ...planErrors.slice(0, 3)]);\n\t}\n\n\tlet journal: ReturnType<WorkloadShardStore[\"load\"]>;\n\ttry {\n\t\tjournal = input.store.load();\n\t} catch (error) {\n\t\t// §21 fail closed: corrupt evidence is quarantined, never silently rebuilt.\n\t\tif (error instanceof WorkloadShardJournalError) {\n\t\t\tinput.store.quarantine();\n\t\t\treturn blockedEvidence(input, now, [`journal.${error.code}`]);\n\t\t}\n\t\tthrow error;\n\t}\n\tif (journal.plan === null) {\n\t\tinput.store.appendPlan(input.plan);\n\t} else if (journal.plan.planId !== input.plan.planId || journal.plan.commandDigest !== input.plan.commandDigest) {\n\t\treturn blockedEvidence(input, now, [\"plan.mismatch\"]);\n\t}\n\n\tconst projections = new Map<string, WorkloadShardProjection>(\n\t\tjournal.projections ?? reduceShardRecords(input.plan, []),\n\t);\n\tconst effectiveConcurrency = Math.max(1, Math.min(input.plan.maxConcurrency, input.decision.maxHeavyProcesses));\n\n\t// §13.5 steps 2-4: re-arm recoverable shards through explicit pending records.\n\tconst resume = planShardResume(input.plan, projections);\n\tconst rearm = [\n\t\t...resume.interrupted,\n\t\t...(input.retryFailed ? resume.retryable : []),\n\t\t...((input.resumeAborted ?? true) ? resume.resumable : []),\n\t];\n\tfor (const shardId of rearm) {\n\t\t// §13.5 step 2: a crash-orphaned `running` shard is first projected as\n\t\t// interrupted (running -> pending is illegal in §13.4), then re-armed.\n\t\tif (projections.get(shardId)?.state === \"running\") {\n\t\t\tapplyTransition(input, projections, shardId, \"interrupted\", { reasonCode: \"recovery.orphaned\", now });\n\t\t}\n\t\tapplyTransition(input, projections, shardId, \"pending\", { reasonCode: \"recovery.rearm\", now });\n\t}\n\n\t// Dependency-wave scheduler: launch ready shards up to the recomputed width.\n\tconst remaining = new Set(\n\t\tinput.plan.shards.map((shard) => shard.shardId).filter((id) => projections.get(id)?.state !== \"passed\"),\n\t);\n\tlet active = 0;\n\tlet wake: (() => void) | undefined;\n\tconst waitTurn = () =>\n\t\tnew Promise<void>((resolve) => {\n\t\t\twake = resolve;\n\t\t});\n\tconst running = new Map<string, Promise<void>>();\n\n\twhile (remaining.size > 0 && !input.signal?.aborted) {\n\t\tconst ready = [...remaining].filter((id) => {\n\t\t\tconst projection = projections.get(id);\n\t\t\tif (projection?.state !== \"pending\") return false;\n\t\t\tconst spec = shardById(input.plan, id);\n\t\t\treturn spec.dependencyIds.every((dep) => projections.get(dep)?.state === \"passed\");\n\t\t});\n\t\tconst launchable = ready.slice(0, Math.max(0, effectiveConcurrency - active));\n\t\tif (launchable.length === 0) {\n\t\t\tif (running.size === 0) break; // deadlock = blocked dependents; aggregate reports them\n\t\t\tawait Promise.race([...running.values(), waitTurn()]);\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const shardId of launchable) {\n\t\t\tremaining.delete(shardId);\n\t\t\tactive += 1;\n\t\t\tconst task = runOneShard(input, projections, shardId, now).finally(() => {\n\t\t\t\tactive -= 1;\n\t\t\t\trunning.delete(shardId);\n\t\t\t\twake?.();\n\t\t\t});\n\t\t\trunning.set(shardId, task);\n\t\t}\n\t}\n\tawait Promise.all(running.values());\n\n\treturn aggregate(input, projections, effectiveConcurrency, now, input.signal?.aborted ? [\"run.aborted\"] : []);\n}\n\nfunction aggregate(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: ReadonlyMap<string, WorkloadShardProjection>,\n\teffectiveConcurrency: number,\n\tnow: () => Date,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: 0,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\tconst shards: ShardResultSummary[] = input.plan.shards.map((shard) => {\n\t\tconst projection = projections.get(shard.shardId);\n\t\tconst state = projection?.state ?? \"pending\";\n\t\tcounts[state] += 1;\n\t\treturn {\n\t\t\tshardId: shard.shardId,\n\t\t\tstate,\n\t\t\tattempt: projection?.attempt ?? 0,\n\t\t\tevidenceRefs: projection?.evidenceRefs ?? [],\n\t\t\t...(projection?.reasonCode !== undefined ? { reasonCode: projection.reasonCode } : {}),\n\t\t};\n\t});\n\tconst verdict = aggregateVerdict(counts, input.plan.shards.length, extraReasons);\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict,\n\t\treasonCodes: extraReasons,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards,\n\t\teffectiveConcurrency,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\nfunction blockedEvidence(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tnow: () => Date,\n\treasonCodes: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: input.plan.shards.length,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict: \"blocked\",\n\t\treasonCodes,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards: input.plan.shards.map((shard) => ({\n\t\t\tshardId: shard.shardId,\n\t\t\tstate: \"pending\" as const,\n\t\t\tattempt: 0,\n\t\t\tevidenceRefs: [],\n\t\t})),\n\t\teffectiveConcurrency: 0,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\n/** §13.6: \"passed\" requires EVERY shard to hold terminal passed evidence. */\nfunction aggregateVerdict(\n\tcounts: Readonly<Record<WorkloadShardState, number>>,\n\tshardCount: number,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence[\"verdict\"] {\n\tif (counts.passed === shardCount) return \"passed\";\n\tif (counts.aborted > 0 || extraReasons.includes(\"run.aborted\")) return \"aborted\";\n\treturn \"failed\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"workload-shard-executor.d.ts","sourceRoot":"","sources":["../../src/core/workload-shard-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AACvH,OAAO,EAKN,KAAK,kBAAkB,EACvB,MAAM,0BAA0B,CAAC;AAMlC,YAAY,EAAE,6BAA6B,EAAE,eAAe,EAAE,WAAW,EAAE,CAAC;AAE5E;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,kEAAgE;AAChE,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,IAAI,EAAE,0BAA0B,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9D,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAsB,wBAAwB,CAC7C,KAAK,EAAE,6BAA6B,GAClC,OAAO,CAAC,8BAA8B,CAAC,CAoFzC","sourcesContent":["import type { ExecuteWorkloadShardPlanInput, ShardRunContext, ShardRunner } from \"./workload-shard-execution-types.ts\";\nimport {\n\tplanShardResume,\n\treduceShardRecords,\n\tvalidateWorkloadShardPlan,\n\ttype WorkloadShardProjection,\n\ttype WorkloadShardState,\n} from \"./workload-shard-plan.ts\";\nimport { applyTransition, runOneShard, shardById } from \"./workload-shard-runner.ts\";\nimport { WorkloadShardJournalError, type WorkloadShardStore } from \"./workload-shard-store.ts\";\n\n// The contract lives in `workload-shard-execution-types.ts` so the runner can\n// name its own parameter type without importing this module back.\nexport type { ExecuteWorkloadShardPlanInput, ShardRunContext, ShardRunner };\n\n/**\n * Workload shard executor (OMK v0.97.x roadmap §13, M5 completion slice).\n *\n * Drives a validated `WorkloadShardPlan` through the journalled §13.4 state\n * machine: passed shards are terminal and never re-run (§13.5 step 3),\n * crash-orphaned `running` shards are recovered through `pending` re-arm\n * records, concurrency is recomputed from the CURRENT admission decision\n * (§13.5 step 6) plus the shared permit pool, and completion produces an\n * aggregate evidence receipt (§13.6) — an observation input for\n * `evaluateTask()`, never a task verdict by itself.\n *\n * Fail-closed rules (§21): a corrupt journal is quarantined and the run is\n * `blocked`; a journal written for a different plan is `blocked`; an invalid\n * plan never starts. The executor never rewrites shard commands (§12.3) —\n * it only runs what the plan's descriptors already describe via the\n * injected runner.\n */\n\nexport interface ShardResultSummary {\n\treadonly shardId: string;\n\treadonly state: WorkloadShardState;\n\treadonly attempt: number;\n\treadonly evidenceRefs: readonly string[];\n\treadonly reasonCode?: string;\n}\n\n/** §13.6 aggregate evidence receipt; §15.1 observation kind. */\nexport interface WorkloadShardAggregateEvidence {\n\treadonly kind: \"workload_shard_result.v1\";\n\treadonly planId: string;\n\treadonly promptRunId: string;\n\treadonly commandDigest: string;\n\treadonly strategy: string;\n\treadonly verdict: \"passed\" | \"failed\" | \"aborted\" | \"blocked\";\n\treadonly reasonCodes: readonly string[];\n\treadonly shardCount: number;\n\treadonly counts: Readonly<Record<WorkloadShardState, number>>;\n\treadonly shards: readonly ShardResultSummary[];\n\treadonly effectiveConcurrency: number;\n\treadonly completedAt: string;\n}\n\nexport async function executeWorkloadShardPlan(\n\tinput: ExecuteWorkloadShardPlanInput,\n): Promise<WorkloadShardAggregateEvidence> {\n\tconst now = input.now ?? (() => new Date());\n\tconst planErrors = validateWorkloadShardPlan(input.plan);\n\tif (planErrors.length > 0) {\n\t\treturn blockedEvidence(input, now, [\"plan.invalid\", ...planErrors.slice(0, 3)]);\n\t}\n\n\tlet journal: ReturnType<WorkloadShardStore[\"load\"]>;\n\ttry {\n\t\tjournal = input.store.load();\n\t} catch (error) {\n\t\t// §21 fail closed: corrupt evidence is quarantined, never silently rebuilt.\n\t\tif (error instanceof WorkloadShardJournalError) {\n\t\t\tinput.store.quarantine();\n\t\t\treturn blockedEvidence(input, now, [`journal.${error.code}`]);\n\t\t}\n\t\tthrow error;\n\t}\n\tif (journal.plan === null) {\n\t\tinput.store.appendPlan(input.plan);\n\t} else if (journal.plan.planId !== input.plan.planId || journal.plan.commandDigest !== input.plan.commandDigest) {\n\t\treturn blockedEvidence(input, now, [\"plan.mismatch\"]);\n\t}\n\n\tconst projections = new Map<string, WorkloadShardProjection>(\n\t\tjournal.projections ?? reduceShardRecords(input.plan, []),\n\t);\n\tconst effectiveConcurrency = Math.max(1, Math.min(input.plan.maxConcurrency, input.decision.maxHeavyProcesses));\n\n\t// §13.5 steps 2-4: re-arm recoverable shards through explicit pending records.\n\tconst resume = planShardResume(input.plan, projections);\n\tconst rearm = [\n\t\t...resume.interrupted,\n\t\t...(input.retryFailed ? resume.retryable : []),\n\t\t...((input.resumeAborted ?? true) ? resume.resumable : []),\n\t];\n\tfor (const shardId of rearm) {\n\t\t// §13.5 step 2: a crash-orphaned `running` shard is first projected as\n\t\t// interrupted (running -> pending is illegal in §13.4), then re-armed.\n\t\tif (projections.get(shardId)?.state === \"running\") {\n\t\t\tapplyTransition(input, projections, shardId, \"interrupted\", { reasonCode: \"recovery.orphaned\", now });\n\t\t}\n\t\tapplyTransition(input, projections, shardId, \"pending\", { reasonCode: \"recovery.rearm\", now });\n\t}\n\n\t// Dependency-wave scheduler: launch ready shards up to the recomputed width.\n\tconst remaining = new Set(\n\t\tinput.plan.shards.map((shard) => shard.shardId).filter((id) => projections.get(id)?.state !== \"passed\"),\n\t);\n\tlet active = 0;\n\tlet wake: (() => void) | undefined;\n\tconst waitTurn = () =>\n\t\tnew Promise<void>((resolve) => {\n\t\t\twake = resolve;\n\t\t});\n\tconst running = new Map<string, Promise<void>>();\n\n\twhile (remaining.size > 0 && !input.signal?.aborted) {\n\t\tconst ready = [...remaining].filter((id) => {\n\t\t\tconst projection = projections.get(id);\n\t\t\tif (projection?.state !== \"pending\") return false;\n\t\t\tconst spec = shardById(input.plan, id);\n\t\t\treturn spec.dependencyIds.every((dep) => projections.get(dep)?.state === \"passed\");\n\t\t});\n\t\tconst launchable = ready.slice(0, Math.max(0, effectiveConcurrency - active));\n\t\tif (launchable.length === 0) {\n\t\t\tif (running.size === 0) break; // deadlock = blocked dependents; aggregate reports them\n\t\t\tawait Promise.race([...running.values(), waitTurn()]);\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const shardId of launchable) {\n\t\t\tremaining.delete(shardId);\n\t\t\tactive += 1;\n\t\t\tconst task = runOneShard(input, projections, shardId, now).finally(() => {\n\t\t\t\tactive -= 1;\n\t\t\t\trunning.delete(shardId);\n\t\t\t\twake?.();\n\t\t\t});\n\t\t\trunning.set(shardId, task);\n\t\t}\n\t}\n\tawait Promise.all(running.values());\n\n\treturn aggregate(input, projections, effectiveConcurrency, now, input.signal?.aborted ? [\"run.aborted\"] : []);\n}\n\nfunction aggregate(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: ReadonlyMap<string, WorkloadShardProjection>,\n\teffectiveConcurrency: number,\n\tnow: () => Date,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: 0,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\tconst shards: ShardResultSummary[] = input.plan.shards.map((shard) => {\n\t\tconst projection = projections.get(shard.shardId);\n\t\tconst state = projection?.state ?? \"pending\";\n\t\tcounts[state] += 1;\n\t\treturn {\n\t\t\tshardId: shard.shardId,\n\t\t\tstate,\n\t\t\tattempt: projection?.attempt ?? 0,\n\t\t\tevidenceRefs: projection?.evidenceRefs ?? [],\n\t\t\t...(projection?.reasonCode !== undefined ? { reasonCode: projection.reasonCode } : {}),\n\t\t};\n\t});\n\tconst verdict = aggregateVerdict(counts, input.plan.shards.length, extraReasons);\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict,\n\t\treasonCodes: extraReasons,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards,\n\t\teffectiveConcurrency,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\nfunction blockedEvidence(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tnow: () => Date,\n\treasonCodes: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: input.plan.shards.length,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict: \"blocked\",\n\t\treasonCodes,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards: input.plan.shards.map((shard) => ({\n\t\t\tshardId: shard.shardId,\n\t\t\tstate: \"pending\" as const,\n\t\t\tattempt: 0,\n\t\t\tevidenceRefs: [],\n\t\t})),\n\t\teffectiveConcurrency: 0,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\n/** §13.6: \"passed\" requires EVERY shard to hold terminal passed evidence. */\nfunction aggregateVerdict(\n\tcounts: Readonly<Record<WorkloadShardState, number>>,\n\tshardCount: number,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence[\"verdict\"] {\n\tif (counts.passed === shardCount) return \"passed\";\n\tif (counts.aborted > 0 || extraReasons.includes(\"run.aborted\")) return \"aborted\";\n\treturn \"failed\";\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workload-shard-executor.js","sourceRoot":"","sources":["../../src/core/workload-shard-executor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ResourceAdmissionDecision } from \"./resource-admission.ts\";\nimport type { WorkloadPermitPool } from \"./workload-permit-pool.ts\";\nimport {\n\tplanShardResume,\n\treduceShardRecords,\n\tvalidateWorkloadShardPlan,\n\ttype WorkloadShardPlan,\n\ttype WorkloadShardProjection,\n\ttype WorkloadShardSpec,\n\ttype WorkloadShardState,\n} from \"./workload-shard-plan.ts\";\nimport { applyTransition, runOneShard, shardById } from \"./workload-shard-runner.ts\";\nimport { WorkloadShardJournalError, type WorkloadShardStore } from \"./workload-shard-store.ts\";\n\n/**\n * Workload shard executor (OMK v0.97.x roadmap §13, M5 completion slice).\n *\n * Drives a validated `WorkloadShardPlan` through the journalled §13.4 state\n * machine: passed shards are terminal and never re-run (§13.5 step 3),\n * crash-orphaned `running` shards are recovered through `pending` re-arm\n * records, concurrency is recomputed from the CURRENT admission decision\n * (§13.5 step 6) plus the shared permit pool, and completion produces an\n * aggregate evidence receipt (§13.6) — an observation input for\n * `evaluateTask()`, never a task verdict by itself.\n *\n * Fail-closed rules (§21): a corrupt journal is quarantined and the run is\n * `blocked`; a journal written for a different plan is `blocked`; an invalid\n * plan never starts. The executor never rewrites shard commands (§12.3) —\n * it only runs what the plan's descriptors already describe via the\n * injected runner.\n */\n\nexport interface ShardRunContext {\n\treadonly shard: WorkloadShardSpec;\n\treadonly attempt: number;\n\treadonly signal?: AbortSignal;\n}\n\nexport type ShardRunner = (\n\tcontext: ShardRunContext,\n) => Promise<{ readonly exitCode: number; readonly evidenceRefs?: readonly string[] }>;\n\nexport interface ExecuteWorkloadShardPlanInput {\n\treadonly plan: WorkloadShardPlan;\n\treadonly store: WorkloadShardStore;\n\treadonly runner: ShardRunner;\n\treadonly decision: ResourceAdmissionDecision;\n\treadonly permitPool?: WorkloadPermitPool;\n\treadonly signal?: AbortSignal;\n\t/** §13.5 step 4 retry policy: re-arm previously failed shards. Default false. */\n\treadonly retryFailed?: boolean;\n\t/** §13.4: aborted -> pending needs an explicit resume; this call is that act. Default true. */\n\treadonly resumeAborted?: boolean;\n\treadonly permitWaitTimeoutMs?: number;\n\treadonly now?: () => Date;\n}\n\nexport interface ShardResultSummary {\n\treadonly shardId: string;\n\treadonly state: WorkloadShardState;\n\treadonly attempt: number;\n\treadonly evidenceRefs: readonly string[];\n\treadonly reasonCode?: string;\n}\n\n/** §13.6 aggregate evidence receipt; §15.1 observation kind. */\nexport interface WorkloadShardAggregateEvidence {\n\treadonly kind: \"workload_shard_result.v1\";\n\treadonly planId: string;\n\treadonly promptRunId: string;\n\treadonly commandDigest: string;\n\treadonly strategy: string;\n\treadonly verdict: \"passed\" | \"failed\" | \"aborted\" | \"blocked\";\n\treadonly reasonCodes: readonly string[];\n\treadonly shardCount: number;\n\treadonly counts: Readonly<Record<WorkloadShardState, number>>;\n\treadonly shards: readonly ShardResultSummary[];\n\treadonly effectiveConcurrency: number;\n\treadonly completedAt: string;\n}\n\nexport async function executeWorkloadShardPlan(\n\tinput: ExecuteWorkloadShardPlanInput,\n): Promise<WorkloadShardAggregateEvidence> {\n\tconst now = input.now ?? (() => new Date());\n\tconst planErrors = validateWorkloadShardPlan(input.plan);\n\tif (planErrors.length > 0) {\n\t\treturn blockedEvidence(input, now, [\"plan.invalid\", ...planErrors.slice(0, 3)]);\n\t}\n\n\tlet journal: ReturnType<WorkloadShardStore[\"load\"]>;\n\ttry {\n\t\tjournal = input.store.load();\n\t} catch (error) {\n\t\t// §21 fail closed: corrupt evidence is quarantined, never silently rebuilt.\n\t\tif (error instanceof WorkloadShardJournalError) {\n\t\t\tinput.store.quarantine();\n\t\t\treturn blockedEvidence(input, now, [`journal.${error.code}`]);\n\t\t}\n\t\tthrow error;\n\t}\n\tif (journal.plan === null) {\n\t\tinput.store.appendPlan(input.plan);\n\t} else if (journal.plan.planId !== input.plan.planId || journal.plan.commandDigest !== input.plan.commandDigest) {\n\t\treturn blockedEvidence(input, now, [\"plan.mismatch\"]);\n\t}\n\n\tconst projections = new Map<string, WorkloadShardProjection>(\n\t\tjournal.projections ?? reduceShardRecords(input.plan, []),\n\t);\n\tconst effectiveConcurrency = Math.max(1, Math.min(input.plan.maxConcurrency, input.decision.maxHeavyProcesses));\n\n\t// §13.5 steps 2-4: re-arm recoverable shards through explicit pending records.\n\tconst resume = planShardResume(input.plan, projections);\n\tconst rearm = [\n\t\t...resume.interrupted,\n\t\t...(input.retryFailed ? resume.retryable : []),\n\t\t...((input.resumeAborted ?? true) ? resume.resumable : []),\n\t];\n\tfor (const shardId of rearm) {\n\t\t// §13.5 step 2: a crash-orphaned `running` shard is first projected as\n\t\t// interrupted (running -> pending is illegal in §13.4), then re-armed.\n\t\tif (projections.get(shardId)?.state === \"running\") {\n\t\t\tapplyTransition(input, projections, shardId, \"interrupted\", { reasonCode: \"recovery.orphaned\", now });\n\t\t}\n\t\tapplyTransition(input, projections, shardId, \"pending\", { reasonCode: \"recovery.rearm\", now });\n\t}\n\n\t// Dependency-wave scheduler: launch ready shards up to the recomputed width.\n\tconst remaining = new Set(\n\t\tinput.plan.shards.map((shard) => shard.shardId).filter((id) => projections.get(id)?.state !== \"passed\"),\n\t);\n\tlet active = 0;\n\tlet wake: (() => void) | undefined;\n\tconst waitTurn = () =>\n\t\tnew Promise<void>((resolve) => {\n\t\t\twake = resolve;\n\t\t});\n\tconst running = new Map<string, Promise<void>>();\n\n\twhile (remaining.size > 0 && !input.signal?.aborted) {\n\t\tconst ready = [...remaining].filter((id) => {\n\t\t\tconst projection = projections.get(id);\n\t\t\tif (projection?.state !== \"pending\") return false;\n\t\t\tconst spec = shardById(input.plan, id);\n\t\t\treturn spec.dependencyIds.every((dep) => projections.get(dep)?.state === \"passed\");\n\t\t});\n\t\tconst launchable = ready.slice(0, Math.max(0, effectiveConcurrency - active));\n\t\tif (launchable.length === 0) {\n\t\t\tif (running.size === 0) break; // deadlock = blocked dependents; aggregate reports them\n\t\t\tawait Promise.race([...running.values(), waitTurn()]);\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const shardId of launchable) {\n\t\t\tremaining.delete(shardId);\n\t\t\tactive += 1;\n\t\t\tconst task = runOneShard(input, projections, shardId, now).finally(() => {\n\t\t\t\tactive -= 1;\n\t\t\t\trunning.delete(shardId);\n\t\t\t\twake?.();\n\t\t\t});\n\t\t\trunning.set(shardId, task);\n\t\t}\n\t}\n\tawait Promise.all(running.values());\n\n\treturn aggregate(input, projections, effectiveConcurrency, now, input.signal?.aborted ? [\"run.aborted\"] : []);\n}\n\nfunction aggregate(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: ReadonlyMap<string, WorkloadShardProjection>,\n\teffectiveConcurrency: number,\n\tnow: () => Date,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: 0,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\tconst shards: ShardResultSummary[] = input.plan.shards.map((shard) => {\n\t\tconst projection = projections.get(shard.shardId);\n\t\tconst state = projection?.state ?? \"pending\";\n\t\tcounts[state] += 1;\n\t\treturn {\n\t\t\tshardId: shard.shardId,\n\t\t\tstate,\n\t\t\tattempt: projection?.attempt ?? 0,\n\t\t\tevidenceRefs: projection?.evidenceRefs ?? [],\n\t\t\t...(projection?.reasonCode !== undefined ? { reasonCode: projection.reasonCode } : {}),\n\t\t};\n\t});\n\tconst verdict = aggregateVerdict(counts, input.plan.shards.length, extraReasons);\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict,\n\t\treasonCodes: extraReasons,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards,\n\t\teffectiveConcurrency,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\nfunction blockedEvidence(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tnow: () => Date,\n\treasonCodes: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: input.plan.shards.length,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict: \"blocked\",\n\t\treasonCodes,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards: input.plan.shards.map((shard) => ({\n\t\t\tshardId: shard.shardId,\n\t\t\tstate: \"pending\" as const,\n\t\t\tattempt: 0,\n\t\t\tevidenceRefs: [],\n\t\t})),\n\t\teffectiveConcurrency: 0,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\n/** §13.6: \"passed\" requires EVERY shard to hold terminal passed evidence. */\nfunction aggregateVerdict(\n\tcounts: Readonly<Record<WorkloadShardState, number>>,\n\tshardCount: number,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence[\"verdict\"] {\n\tif (counts.passed === shardCount) return \"passed\";\n\tif (counts.aborted > 0 || extraReasons.includes(\"run.aborted\")) return \"aborted\";\n\treturn \"failed\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"workload-shard-executor.js","sourceRoot":"","sources":["../../src/core/workload-shard-executor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExecuteWorkloadShardPlanInput, ShardRunContext, ShardRunner } from \"./workload-shard-execution-types.ts\";\nimport {\n\tplanShardResume,\n\treduceShardRecords,\n\tvalidateWorkloadShardPlan,\n\ttype WorkloadShardProjection,\n\ttype WorkloadShardState,\n} from \"./workload-shard-plan.ts\";\nimport { applyTransition, runOneShard, shardById } from \"./workload-shard-runner.ts\";\nimport { WorkloadShardJournalError, type WorkloadShardStore } from \"./workload-shard-store.ts\";\n\n// The contract lives in `workload-shard-execution-types.ts` so the runner can\n// name its own parameter type without importing this module back.\nexport type { ExecuteWorkloadShardPlanInput, ShardRunContext, ShardRunner };\n\n/**\n * Workload shard executor (OMK v0.97.x roadmap §13, M5 completion slice).\n *\n * Drives a validated `WorkloadShardPlan` through the journalled §13.4 state\n * machine: passed shards are terminal and never re-run (§13.5 step 3),\n * crash-orphaned `running` shards are recovered through `pending` re-arm\n * records, concurrency is recomputed from the CURRENT admission decision\n * (§13.5 step 6) plus the shared permit pool, and completion produces an\n * aggregate evidence receipt (§13.6) — an observation input for\n * `evaluateTask()`, never a task verdict by itself.\n *\n * Fail-closed rules (§21): a corrupt journal is quarantined and the run is\n * `blocked`; a journal written for a different plan is `blocked`; an invalid\n * plan never starts. The executor never rewrites shard commands (§12.3) —\n * it only runs what the plan's descriptors already describe via the\n * injected runner.\n */\n\nexport interface ShardResultSummary {\n\treadonly shardId: string;\n\treadonly state: WorkloadShardState;\n\treadonly attempt: number;\n\treadonly evidenceRefs: readonly string[];\n\treadonly reasonCode?: string;\n}\n\n/** §13.6 aggregate evidence receipt; §15.1 observation kind. */\nexport interface WorkloadShardAggregateEvidence {\n\treadonly kind: \"workload_shard_result.v1\";\n\treadonly planId: string;\n\treadonly promptRunId: string;\n\treadonly commandDigest: string;\n\treadonly strategy: string;\n\treadonly verdict: \"passed\" | \"failed\" | \"aborted\" | \"blocked\";\n\treadonly reasonCodes: readonly string[];\n\treadonly shardCount: number;\n\treadonly counts: Readonly<Record<WorkloadShardState, number>>;\n\treadonly shards: readonly ShardResultSummary[];\n\treadonly effectiveConcurrency: number;\n\treadonly completedAt: string;\n}\n\nexport async function executeWorkloadShardPlan(\n\tinput: ExecuteWorkloadShardPlanInput,\n): Promise<WorkloadShardAggregateEvidence> {\n\tconst now = input.now ?? (() => new Date());\n\tconst planErrors = validateWorkloadShardPlan(input.plan);\n\tif (planErrors.length > 0) {\n\t\treturn blockedEvidence(input, now, [\"plan.invalid\", ...planErrors.slice(0, 3)]);\n\t}\n\n\tlet journal: ReturnType<WorkloadShardStore[\"load\"]>;\n\ttry {\n\t\tjournal = input.store.load();\n\t} catch (error) {\n\t\t// §21 fail closed: corrupt evidence is quarantined, never silently rebuilt.\n\t\tif (error instanceof WorkloadShardJournalError) {\n\t\t\tinput.store.quarantine();\n\t\t\treturn blockedEvidence(input, now, [`journal.${error.code}`]);\n\t\t}\n\t\tthrow error;\n\t}\n\tif (journal.plan === null) {\n\t\tinput.store.appendPlan(input.plan);\n\t} else if (journal.plan.planId !== input.plan.planId || journal.plan.commandDigest !== input.plan.commandDigest) {\n\t\treturn blockedEvidence(input, now, [\"plan.mismatch\"]);\n\t}\n\n\tconst projections = new Map<string, WorkloadShardProjection>(\n\t\tjournal.projections ?? reduceShardRecords(input.plan, []),\n\t);\n\tconst effectiveConcurrency = Math.max(1, Math.min(input.plan.maxConcurrency, input.decision.maxHeavyProcesses));\n\n\t// §13.5 steps 2-4: re-arm recoverable shards through explicit pending records.\n\tconst resume = planShardResume(input.plan, projections);\n\tconst rearm = [\n\t\t...resume.interrupted,\n\t\t...(input.retryFailed ? resume.retryable : []),\n\t\t...((input.resumeAborted ?? true) ? resume.resumable : []),\n\t];\n\tfor (const shardId of rearm) {\n\t\t// §13.5 step 2: a crash-orphaned `running` shard is first projected as\n\t\t// interrupted (running -> pending is illegal in §13.4), then re-armed.\n\t\tif (projections.get(shardId)?.state === \"running\") {\n\t\t\tapplyTransition(input, projections, shardId, \"interrupted\", { reasonCode: \"recovery.orphaned\", now });\n\t\t}\n\t\tapplyTransition(input, projections, shardId, \"pending\", { reasonCode: \"recovery.rearm\", now });\n\t}\n\n\t// Dependency-wave scheduler: launch ready shards up to the recomputed width.\n\tconst remaining = new Set(\n\t\tinput.plan.shards.map((shard) => shard.shardId).filter((id) => projections.get(id)?.state !== \"passed\"),\n\t);\n\tlet active = 0;\n\tlet wake: (() => void) | undefined;\n\tconst waitTurn = () =>\n\t\tnew Promise<void>((resolve) => {\n\t\t\twake = resolve;\n\t\t});\n\tconst running = new Map<string, Promise<void>>();\n\n\twhile (remaining.size > 0 && !input.signal?.aborted) {\n\t\tconst ready = [...remaining].filter((id) => {\n\t\t\tconst projection = projections.get(id);\n\t\t\tif (projection?.state !== \"pending\") return false;\n\t\t\tconst spec = shardById(input.plan, id);\n\t\t\treturn spec.dependencyIds.every((dep) => projections.get(dep)?.state === \"passed\");\n\t\t});\n\t\tconst launchable = ready.slice(0, Math.max(0, effectiveConcurrency - active));\n\t\tif (launchable.length === 0) {\n\t\t\tif (running.size === 0) break; // deadlock = blocked dependents; aggregate reports them\n\t\t\tawait Promise.race([...running.values(), waitTurn()]);\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const shardId of launchable) {\n\t\t\tremaining.delete(shardId);\n\t\t\tactive += 1;\n\t\t\tconst task = runOneShard(input, projections, shardId, now).finally(() => {\n\t\t\t\tactive -= 1;\n\t\t\t\trunning.delete(shardId);\n\t\t\t\twake?.();\n\t\t\t});\n\t\t\trunning.set(shardId, task);\n\t\t}\n\t}\n\tawait Promise.all(running.values());\n\n\treturn aggregate(input, projections, effectiveConcurrency, now, input.signal?.aborted ? [\"run.aborted\"] : []);\n}\n\nfunction aggregate(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: ReadonlyMap<string, WorkloadShardProjection>,\n\teffectiveConcurrency: number,\n\tnow: () => Date,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: 0,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\tconst shards: ShardResultSummary[] = input.plan.shards.map((shard) => {\n\t\tconst projection = projections.get(shard.shardId);\n\t\tconst state = projection?.state ?? \"pending\";\n\t\tcounts[state] += 1;\n\t\treturn {\n\t\t\tshardId: shard.shardId,\n\t\t\tstate,\n\t\t\tattempt: projection?.attempt ?? 0,\n\t\t\tevidenceRefs: projection?.evidenceRefs ?? [],\n\t\t\t...(projection?.reasonCode !== undefined ? { reasonCode: projection.reasonCode } : {}),\n\t\t};\n\t});\n\tconst verdict = aggregateVerdict(counts, input.plan.shards.length, extraReasons);\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict,\n\t\treasonCodes: extraReasons,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards,\n\t\teffectiveConcurrency,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\nfunction blockedEvidence(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tnow: () => Date,\n\treasonCodes: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: input.plan.shards.length,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict: \"blocked\",\n\t\treasonCodes,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards: input.plan.shards.map((shard) => ({\n\t\t\tshardId: shard.shardId,\n\t\t\tstate: \"pending\" as const,\n\t\t\tattempt: 0,\n\t\t\tevidenceRefs: [],\n\t\t})),\n\t\teffectiveConcurrency: 0,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\n/** §13.6: \"passed\" requires EVERY shard to hold terminal passed evidence. */\nfunction aggregateVerdict(\n\tcounts: Readonly<Record<WorkloadShardState, number>>,\n\tshardCount: number,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence[\"verdict\"] {\n\tif (counts.passed === shardCount) return \"passed\";\n\tif (counts.aborted > 0 || extraReasons.includes(\"run.aborted\")) return \"aborted\";\n\treturn \"failed\";\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExecuteWorkloadShardPlanInput } from "./workload-shard-
|
|
1
|
+
import type { ExecuteWorkloadShardPlanInput } from "./workload-shard-execution-types.ts";
|
|
2
2
|
import type { WorkloadShardPlan, WorkloadShardProjection, WorkloadShardSpec, WorkloadShardState } from "./workload-shard-plan.ts";
|
|
3
3
|
export declare function runOneShard(input: ExecuteWorkloadShardPlanInput, projections: Map<string, WorkloadShardProjection>, shardId: string, now: () => Date): Promise<void>;
|
|
4
4
|
export declare function applyTransition(input: ExecuteWorkloadShardPlanInput, projections: Map<string, WorkloadShardProjection>, shardId: string, state: WorkloadShardState, options: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workload-shard-runner.d.ts","sourceRoot":"","sources":["../../src/core/workload-shard-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"workload-shard-runner.d.ts","sourceRoot":"","sources":["../../src/core/workload-shard-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AACzF,OAAO,KAAK,EACX,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,MAAM,0BAA0B,CAAC;AAElC,wBAAsB,WAAW,CAChC,KAAK,EAAE,6BAA6B,EACpC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EACjD,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,IAAI,GACb,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED,wBAAgB,eAAe,CAC9B,KAAK,EAAE,6BAA6B,EACpC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EACjD,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE;IACR,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B,GACC,IAAI,CAuBN;AAYD,wBAAgB,SAAS,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAMrF","sourcesContent":["import { WorkloadPermitError } from \"./workload-permit-pool.ts\";\nimport type { ExecuteWorkloadShardPlanInput } from \"./workload-shard-execution-types.ts\";\nimport type {\n\tWorkloadShardPlan,\n\tWorkloadShardProjection,\n\tWorkloadShardSpec,\n\tWorkloadShardState,\n} from \"./workload-shard-plan.ts\";\n\nexport async function runOneShard(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: Map<string, WorkloadShardProjection>,\n\tshardId: string,\n\tnow: () => Date,\n): Promise<void> {\n\tconst shard = shardById(input.plan, shardId);\n\tlet releasePermit: (() => void) | undefined;\n\tif (input.permitPool) {\n\t\ttry {\n\t\t\tconst permit = await input.permitPool.acquire({\n\t\t\t\trequestId: `shard-${input.plan.planId}-${shardId}`,\n\t\t\t\tpromptRunId: input.plan.promptRunId,\n\t\t\t\tworkloadClass: \"heavy\",\n\t\t\t\tweight: 1,\n\t\t\t\tsignal: input.signal,\n\t\t\t\ttimeoutMs: input.permitWaitTimeoutMs ?? 120_000,\n\t\t\t});\n\t\t\treleasePermit = () => permit.release();\n\t\t} catch (error) {\n\t\t\tconst aborted = error instanceof WorkloadPermitError && error.code === \"aborted\";\n\t\t\t// Never ran: stays pending; an aborted wait is not a shard failure.\n\t\t\tif (!aborted) {\n\t\t\t\tapplyTransition(input, projections, shardId, \"running\", { now });\n\t\t\t\tapplyTransition(input, projections, shardId, \"failed\", { reasonCode: \"permit.unavailable\", now });\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\tconst startedAt = now().toISOString();\n\tapplyTransition(input, projections, shardId, \"running\", { now, startedAt });\n\ttry {\n\t\tconst result = await input.runner({\n\t\t\tshard,\n\t\t\tattempt: projections.get(shardId)?.attempt ?? 1,\n\t\t\tsignal: input.signal,\n\t\t});\n\t\tconst state = terminalStateFor(input.signal, result.exitCode);\n\t\tapplyTransition(input, projections, shardId, state, {\n\t\t\tnow,\n\t\t\tstartedAt,\n\t\t\tevidenceRefs: [`exit-code:${result.exitCode}`, ...(result.evidenceRefs ?? [])],\n\t\t\treasonCode: terminalReasonFor(state, result.exitCode),\n\t\t});\n\t} catch (error) {\n\t\tconst state: WorkloadShardState = input.signal?.aborted ? \"aborted\" : \"failed\";\n\t\tapplyTransition(input, projections, shardId, state, {\n\t\t\tnow,\n\t\t\tstartedAt,\n\t\t\treasonCode: state === \"aborted\" ? \"run.aborted\" : \"runner.error\",\n\t\t\tevidenceRefs: [`error:${String(error).slice(0, 120)}`],\n\t\t});\n\t} finally {\n\t\treleasePermit?.();\n\t}\n}\n\nexport function applyTransition(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: Map<string, WorkloadShardProjection>,\n\tshardId: string,\n\tstate: WorkloadShardState,\n\toptions: {\n\t\treadonly now: () => Date;\n\t\treadonly startedAt?: string;\n\t\treadonly evidenceRefs?: readonly string[];\n\t\treadonly reasonCode?: string;\n\t},\n): void {\n\tconst current = projections.get(shardId);\n\tif (current === undefined) return;\n\tconst attempt = state === \"running\" ? current.attempt + 1 : current.attempt;\n\tconst terminal = state !== \"pending\" && state !== \"running\";\n\tinput.store.appendTransition({\n\t\tschemaVersion: 1,\n\t\tplanId: input.plan.planId,\n\t\tshardId,\n\t\tattempt,\n\t\tstate,\n\t\t...(options.startedAt !== undefined && state === \"running\" ? { startedAt: options.startedAt } : {}),\n\t\t...(terminal ? { finishedAt: options.now().toISOString() } : {}),\n\t\tevidenceRefs: options.evidenceRefs ?? [],\n\t\t...(options.reasonCode !== undefined ? { reasonCode: options.reasonCode } : {}),\n\t});\n\tprojections.set(shardId, {\n\t\tshardId,\n\t\tstate,\n\t\tattempt,\n\t\tevidenceRefs: options.evidenceRefs?.length ? options.evidenceRefs : current.evidenceRefs,\n\t\treasonCode: options.reasonCode ?? (state === \"pending\" ? undefined : current.reasonCode),\n\t});\n}\n\nfunction terminalStateFor(signal: AbortSignal | undefined, exitCode: number): WorkloadShardState {\n\tif (signal?.aborted) return \"aborted\";\n\treturn exitCode === 0 ? \"passed\" : \"failed\";\n}\n\nfunction terminalReasonFor(state: WorkloadShardState, exitCode: number): string | undefined {\n\tif (state === \"failed\") return `exit.${exitCode}`;\n\treturn state === \"aborted\" ? \"run.aborted\" : undefined;\n}\n\nexport function shardById(plan: WorkloadShardPlan, shardId: string): WorkloadShardSpec {\n\tconst shard = plan.shards.find((candidate) => candidate.shardId === shardId);\n\tif (shard === undefined) {\n\t\tthrow new Error(`shard ${shardId} not in plan ${plan.planId}`);\n\t}\n\treturn shard;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workload-shard-runner.js","sourceRoot":"","sources":["../../src/core/workload-shard-runner.ts"],"names":[],"mappings":"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","sourcesContent":["import { WorkloadPermitError } from \"./workload-permit-pool.ts\";\nimport type { ExecuteWorkloadShardPlanInput } from \"./workload-shard-
|
|
1
|
+
{"version":3,"file":"workload-shard-runner.js","sourceRoot":"","sources":["../../src/core/workload-shard-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAShE,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,KAAoC,EACpC,WAAiD,EACjD,OAAe,EACf,GAAe,EACC;IAChB,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAI,aAAuC,CAAC;IAC5C,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACtB,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;gBAC7C,SAAS,EAAE,SAAS,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,EAAE;gBAClD,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW;gBACnC,aAAa,EAAE,OAAO;gBACtB,MAAM,EAAE,CAAC;gBACT,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,SAAS,EAAE,KAAK,CAAC,mBAAmB,IAAI,OAAO;aAC/C,CAAC,CAAC;YACH,aAAa,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACxC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,mBAAmB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;YACjF,oEAAoE;YACpE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACd,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;gBACjE,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC;YACnG,CAAC;YACD,OAAO;QACR,CAAC;IACF,CAAC;IACD,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5E,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC;YACjC,KAAK;YACL,OAAO,EAAE,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,IAAI,CAAC;YAC/C,MAAM,EAAE,KAAK,CAAC,MAAM;SACpB,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9D,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE;YACnD,GAAG;YACH,SAAS;YACT,YAAY,EAAE,CAAC,aAAa,MAAM,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;YAC9E,UAAU,EAAE,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC;SACrD,CAAC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,KAAK,GAAuB,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC/E,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE;YACnD,GAAG;YACH,SAAS;YACT,UAAU,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc;YAChE,YAAY,EAAE,CAAC,SAAS,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;SACtD,CAAC,CAAC;IACJ,CAAC;YAAS,CAAC;QACV,aAAa,EAAE,EAAE,CAAC;IACnB,CAAC;AAAA,CACD;AAED,MAAM,UAAU,eAAe,CAC9B,KAAoC,EACpC,WAAiD,EACjD,OAAe,EACf,KAAyB,EACzB,OAKC,EACM;IACP,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO;IAClC,MAAM,OAAO,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IAC5E,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,CAAC;IAC5D,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC;QAC5B,aAAa,EAAE,CAAC;QAChB,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM;QACzB,OAAO;QACP,OAAO;QACP,KAAK;QACL,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,EAAE;QACxC,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/E,CAAC,CAAC;IACH,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE;QACxB,OAAO;QACP,KAAK;QACL,OAAO;QACP,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY;QACxF,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;KACxF,CAAC,CAAC;AAAA,CACH;AAED,SAAS,gBAAgB,CAAC,MAA+B,EAAE,QAAgB,EAAsB;IAChG,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,SAAS,CAAC;IACtC,OAAO,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AAAA,CAC5C;AAED,SAAS,iBAAiB,CAAC,KAAyB,EAAE,QAAgB,EAAsB;IAC3F,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,QAAQ,QAAQ,EAAE,CAAC;IAClD,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CACvD;AAED,MAAM,UAAU,SAAS,CAAC,IAAuB,EAAE,OAAe,EAAqB;IACtF,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;IAC7E,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,SAAS,OAAO,gBAAgB,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb","sourcesContent":["import { WorkloadPermitError } from \"./workload-permit-pool.ts\";\nimport type { ExecuteWorkloadShardPlanInput } from \"./workload-shard-execution-types.ts\";\nimport type {\n\tWorkloadShardPlan,\n\tWorkloadShardProjection,\n\tWorkloadShardSpec,\n\tWorkloadShardState,\n} from \"./workload-shard-plan.ts\";\n\nexport async function runOneShard(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: Map<string, WorkloadShardProjection>,\n\tshardId: string,\n\tnow: () => Date,\n): Promise<void> {\n\tconst shard = shardById(input.plan, shardId);\n\tlet releasePermit: (() => void) | undefined;\n\tif (input.permitPool) {\n\t\ttry {\n\t\t\tconst permit = await input.permitPool.acquire({\n\t\t\t\trequestId: `shard-${input.plan.planId}-${shardId}`,\n\t\t\t\tpromptRunId: input.plan.promptRunId,\n\t\t\t\tworkloadClass: \"heavy\",\n\t\t\t\tweight: 1,\n\t\t\t\tsignal: input.signal,\n\t\t\t\ttimeoutMs: input.permitWaitTimeoutMs ?? 120_000,\n\t\t\t});\n\t\t\treleasePermit = () => permit.release();\n\t\t} catch (error) {\n\t\t\tconst aborted = error instanceof WorkloadPermitError && error.code === \"aborted\";\n\t\t\t// Never ran: stays pending; an aborted wait is not a shard failure.\n\t\t\tif (!aborted) {\n\t\t\t\tapplyTransition(input, projections, shardId, \"running\", { now });\n\t\t\t\tapplyTransition(input, projections, shardId, \"failed\", { reasonCode: \"permit.unavailable\", now });\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\tconst startedAt = now().toISOString();\n\tapplyTransition(input, projections, shardId, \"running\", { now, startedAt });\n\ttry {\n\t\tconst result = await input.runner({\n\t\t\tshard,\n\t\t\tattempt: projections.get(shardId)?.attempt ?? 1,\n\t\t\tsignal: input.signal,\n\t\t});\n\t\tconst state = terminalStateFor(input.signal, result.exitCode);\n\t\tapplyTransition(input, projections, shardId, state, {\n\t\t\tnow,\n\t\t\tstartedAt,\n\t\t\tevidenceRefs: [`exit-code:${result.exitCode}`, ...(result.evidenceRefs ?? [])],\n\t\t\treasonCode: terminalReasonFor(state, result.exitCode),\n\t\t});\n\t} catch (error) {\n\t\tconst state: WorkloadShardState = input.signal?.aborted ? \"aborted\" : \"failed\";\n\t\tapplyTransition(input, projections, shardId, state, {\n\t\t\tnow,\n\t\t\tstartedAt,\n\t\t\treasonCode: state === \"aborted\" ? \"run.aborted\" : \"runner.error\",\n\t\t\tevidenceRefs: [`error:${String(error).slice(0, 120)}`],\n\t\t});\n\t} finally {\n\t\treleasePermit?.();\n\t}\n}\n\nexport function applyTransition(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: Map<string, WorkloadShardProjection>,\n\tshardId: string,\n\tstate: WorkloadShardState,\n\toptions: {\n\t\treadonly now: () => Date;\n\t\treadonly startedAt?: string;\n\t\treadonly evidenceRefs?: readonly string[];\n\t\treadonly reasonCode?: string;\n\t},\n): void {\n\tconst current = projections.get(shardId);\n\tif (current === undefined) return;\n\tconst attempt = state === \"running\" ? current.attempt + 1 : current.attempt;\n\tconst terminal = state !== \"pending\" && state !== \"running\";\n\tinput.store.appendTransition({\n\t\tschemaVersion: 1,\n\t\tplanId: input.plan.planId,\n\t\tshardId,\n\t\tattempt,\n\t\tstate,\n\t\t...(options.startedAt !== undefined && state === \"running\" ? { startedAt: options.startedAt } : {}),\n\t\t...(terminal ? { finishedAt: options.now().toISOString() } : {}),\n\t\tevidenceRefs: options.evidenceRefs ?? [],\n\t\t...(options.reasonCode !== undefined ? { reasonCode: options.reasonCode } : {}),\n\t});\n\tprojections.set(shardId, {\n\t\tshardId,\n\t\tstate,\n\t\tattempt,\n\t\tevidenceRefs: options.evidenceRefs?.length ? options.evidenceRefs : current.evidenceRefs,\n\t\treasonCode: options.reasonCode ?? (state === \"pending\" ? undefined : current.reasonCode),\n\t});\n}\n\nfunction terminalStateFor(signal: AbortSignal | undefined, exitCode: number): WorkloadShardState {\n\tif (signal?.aborted) return \"aborted\";\n\treturn exitCode === 0 ? \"passed\" : \"failed\";\n}\n\nfunction terminalReasonFor(state: WorkloadShardState, exitCode: number): string | undefined {\n\tif (state === \"failed\") return `exit.${exitCode}`;\n\treturn state === \"aborted\" ? \"run.aborted\" : undefined;\n}\n\nexport function shardById(plan: WorkloadShardPlan, shardId: string): WorkloadShardSpec {\n\tconst shard = plan.shards.find((candidate) => candidate.shardId === shardId);\n\tif (shard === undefined) {\n\t\tthrow new Error(`shard ${shardId} not in plan ${plan.planId}`);\n\t}\n\treturn shard;\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workload classification vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* These declarations sat in `workload-classifier.ts`, which imports the family
|
|
5
|
+
* matcher, while the matcher imported the declarations back — a two-module
|
|
6
|
+
* import cycle. Neither module was wrong to want the other: the classifier owns
|
|
7
|
+
* the decision, the matcher owns the command-family table, and both speak the
|
|
8
|
+
* same vocabulary. Giving the vocabulary its own module lets both depend on it
|
|
9
|
+
* without depending on each other.
|
|
10
|
+
*
|
|
11
|
+
* Types only, no imports: this module is the bottom of that dependency chain
|
|
12
|
+
* and must stay there.
|
|
13
|
+
*/
|
|
14
|
+
export type WorkloadClass = "light" | "io" | "cpu" | "memory" | "heavy" | "unknown";
|
|
15
|
+
export type WorkloadCommandFamily = "node-test" | "node-build" | "typescript" | "go-test" | "rust-build" | "container-build" | "monorepo" | "archive" | "generic-process" | "unknown";
|
|
16
|
+
export type WorkloadShellComplexity = "simple-argv" | "complex-shell";
|
|
17
|
+
export interface WorkloadClassification {
|
|
18
|
+
readonly workloadClass: WorkloadClass;
|
|
19
|
+
readonly commandFamily: WorkloadCommandFamily;
|
|
20
|
+
readonly complexity: WorkloadShellComplexity;
|
|
21
|
+
readonly safeToAutoShard: boolean;
|
|
22
|
+
readonly reasonCodes: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=workload-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload-types.d.ts","sourceRoot":"","sources":["../../src/core/workload-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAEpF,MAAM,MAAM,qBAAqB,GAC9B,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,SAAS,GACT,YAAY,GACZ,iBAAiB,GACjB,UAAU,GACV,SAAS,GACT,iBAAiB,GACjB,SAAS,CAAC;AAEb,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG,eAAe,CAAC;AAEtE,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,uBAAuB,CAAC;IAC7C,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC","sourcesContent":["/**\n * Workload classification vocabulary.\n *\n * These declarations sat in `workload-classifier.ts`, which imports the family\n * matcher, while the matcher imported the declarations back — a two-module\n * import cycle. Neither module was wrong to want the other: the classifier owns\n * the decision, the matcher owns the command-family table, and both speak the\n * same vocabulary. Giving the vocabulary its own module lets both depend on it\n * without depending on each other.\n *\n * Types only, no imports: this module is the bottom of that dependency chain\n * and must stay there.\n */\n\nexport type WorkloadClass = \"light\" | \"io\" | \"cpu\" | \"memory\" | \"heavy\" | \"unknown\";\n\nexport type WorkloadCommandFamily =\n\t| \"node-test\"\n\t| \"node-build\"\n\t| \"typescript\"\n\t| \"go-test\"\n\t| \"rust-build\"\n\t| \"container-build\"\n\t| \"monorepo\"\n\t| \"archive\"\n\t| \"generic-process\"\n\t| \"unknown\";\n\nexport type WorkloadShellComplexity = \"simple-argv\" | \"complex-shell\";\n\nexport interface WorkloadClassification {\n\treadonly workloadClass: WorkloadClass;\n\treadonly commandFamily: WorkloadCommandFamily;\n\treadonly complexity: WorkloadShellComplexity;\n\treadonly safeToAutoShard: boolean;\n\treadonly reasonCodes: readonly string[];\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workload classification vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* These declarations sat in `workload-classifier.ts`, which imports the family
|
|
5
|
+
* matcher, while the matcher imported the declarations back — a two-module
|
|
6
|
+
* import cycle. Neither module was wrong to want the other: the classifier owns
|
|
7
|
+
* the decision, the matcher owns the command-family table, and both speak the
|
|
8
|
+
* same vocabulary. Giving the vocabulary its own module lets both depend on it
|
|
9
|
+
* without depending on each other.
|
|
10
|
+
*
|
|
11
|
+
* Types only, no imports: this module is the bottom of that dependency chain
|
|
12
|
+
* and must stay there.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=workload-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload-types.js","sourceRoot":"","sources":["../../src/core/workload-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG","sourcesContent":["/**\n * Workload classification vocabulary.\n *\n * These declarations sat in `workload-classifier.ts`, which imports the family\n * matcher, while the matcher imported the declarations back — a two-module\n * import cycle. Neither module was wrong to want the other: the classifier owns\n * the decision, the matcher owns the command-family table, and both speak the\n * same vocabulary. Giving the vocabulary its own module lets both depend on it\n * without depending on each other.\n *\n * Types only, no imports: this module is the bottom of that dependency chain\n * and must stay there.\n */\n\nexport type WorkloadClass = \"light\" | \"io\" | \"cpu\" | \"memory\" | \"heavy\" | \"unknown\";\n\nexport type WorkloadCommandFamily =\n\t| \"node-test\"\n\t| \"node-build\"\n\t| \"typescript\"\n\t| \"go-test\"\n\t| \"rust-build\"\n\t| \"container-build\"\n\t| \"monorepo\"\n\t| \"archive\"\n\t| \"generic-process\"\n\t| \"unknown\";\n\nexport type WorkloadShellComplexity = \"simple-argv\" | \"complex-shell\";\n\nexport interface WorkloadClassification {\n\treadonly workloadClass: WorkloadClass;\n\treadonly commandFamily: WorkloadCommandFamily;\n\treadonly complexity: WorkloadShellComplexity;\n\treadonly safeToAutoShard: boolean;\n\treadonly reasonCodes: readonly string[];\n}\n"]}
|
package/dist/main.d.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* This file handles CLI argument parsing and translates them into
|
|
5
5
|
* createAgentSession() options. The SDK does the heavy lifting.
|
|
6
6
|
*/
|
|
7
|
+
import { isExplicitExtensionDiagnostic, resolveCliPaths } from "./cli/resource-paths.ts";
|
|
7
8
|
import type { ExtensionFactory } from "./core/extensions/types.ts";
|
|
9
|
+
export { isExplicitExtensionDiagnostic, resolveCliPaths };
|
|
8
10
|
export interface MainOptions {
|
|
9
11
|
extensionFactories?: ExtensionFactory[];
|
|
10
12
|
}
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA+BH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAyhBnE,MAAM,WAAW,WAAW;IAC3B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACxC;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,iBA8W/D","sourcesContent":["/**\n * Main entry point for the coding agent CLI.\n *\n * This file handles CLI argument parsing and translates them into\n * createAgentSession() options. The SDK does the heavy lifting.\n */\n\nimport * as fs from \"node:fs\";\nimport * as net from \"node:net\";\nimport { createInterface } from \"node:readline\";\nimport chalk from \"chalk\";\nimport { type ImageContent, modelsAreEqual } from \"omk-ai\";\nimport { ProcessTerminal, setKeybindings, TUI } from \"omk-tui\";\nimport { type Args, type Mode, parseArgs, printHelp } from \"./cli/args.ts\";\nimport { processFileArguments } from \"./cli/file-processor.ts\";\nimport { buildInitialMessage } from \"./cli/initial-message.ts\";\nimport { listModels } from \"./cli/list-models.ts\";\nimport { selectSession } from \"./cli/session-picker.ts\";\nimport { handleCodexBarQuotaCommand } from \"./codexbar-cli.ts\";\nimport { runDoctorProviderCli } from \"./commands/doctor-provider-cli.ts\";\nimport { runPackageDoctorCli } from \"./commands/package-doctor-cli.ts\";\nimport { runResourceDoctorCli } from \"./commands/resource-doctor-cli.ts\";\nimport { runRouterFeedbackCli } from \"./commands/router-feedback-cli.ts\";\nimport { runSdkSessionCli } from \"./commands/sdk-session-cli.ts\";\nimport { runSessionDoctorCli } from \"./commands/session-doctor-cli.ts\";\nimport { runStatsCli } from \"./commands/stats-cli.ts\";\nimport { ENV_SESSION_DIR, expandTildePath, getAgentDir, getPackageDir, VERSION } from \"./config.ts\";\nimport { type CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from \"./core/agent-session-runtime.ts\";\nimport {\n\ttype AgentSessionRuntimeDiagnostic,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./core/agent-session-services.ts\";\nimport { formatNoModelsAvailableMessage } from \"./core/auth-guidance.ts\";\nimport { AuthStorage } from \"./core/auth-storage.ts\";\nimport { exportFromFile } from \"./core/export-html/index.ts\";\nimport type { ExtensionFactory } from \"./core/extensions/types.ts\";\nimport { configureHttpDispatcher } from \"./core/http-dispatcher.ts\";\nimport { KeybindingsManager } from \"./core/keybindings.ts\";\nimport type { ModelRegistry } from \"./core/model-registry.ts\";\nimport { resolveCliModel, resolveModelScope, type ScopedModel } from \"./core/model-resolver.ts\";\nimport { restoreStdout, takeOverStdout } from \"./core/output-guard.ts\";\nimport type { CreateAgentSessionOptions } from \"./core/sdk.ts\";\nimport {\n\tformatMissingSessionCwdPrompt,\n\tgetMissingSessionCwdIssue,\n\tMissingSessionCwdError,\n\ttype SessionCwdIssue,\n} from \"./core/session-cwd.ts\";\nimport { assertValidSessionId, SessionManager } from \"./core/session-manager.ts\";\nimport { SettingsManager } from \"./core/settings-manager.ts\";\nimport { printTimings, resetTimings, time } from \"./core/timings.ts\";\nimport { runMigrations, showDeprecationWarnings } from \"./migrations.ts\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.ts\";\nimport { ExtensionSelectorComponent } from \"./modes/interactive/components/extension-selector.ts\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.ts\";\nimport { handleConfigCommand, handlePackageCommand } from \"./package-manager-cli.ts\";\nimport { isLocalPath, normalizePath, resolvePath } from \"./utils/paths.ts\";\nimport { cleanupWindowsSelfUpdateQuarantine } from \"./utils/windows-self-update.ts\";\n\n/**\n * Read all content from piped stdin.\n * Returns undefined if stdin is a TTY (interactive terminal).\n *\n * Non-TTY stdin that never ends (some IDE/terminal hosts keep fd0 open) used to\n * hang forever here — that looked like \"omk\" with no response. Bound the wait:\n * if no bytes arrive quickly and nothing is buffered, treat as empty.\n */\nasync function readPipedStdin(): Promise<string | undefined> {\n\t// If stdin is a TTY, we're running interactively - don't read stdin\n\tif (process.stdin.isTTY) {\n\t\treturn undefined;\n\t}\n\n\t// Already drained\n\tif (process.stdin.readableEnded) {\n\t\treturn undefined;\n\t}\n\n\tconst idleMs = Number(process.env.OMK_STDIN_IDLE_MS || 50);\n\treturn new Promise((resolve) => {\n\t\tlet data = \"\";\n\t\tlet settled = false;\n\t\tconst finish = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tprocess.stdin.off(\"data\", onData);\n\t\t\tprocess.stdin.off(\"end\", onEnd);\n\t\t\tprocess.stdin.off(\"error\", onEnd);\n\t\t\tresolve(data.trim() || undefined);\n\t\t};\n\t\tconst onData = (chunk: string | Buffer) => {\n\t\t\tdata += typeof chunk === \"string\" ? chunk : chunk.toString(\"utf8\");\n\t\t\t// Once data starts flowing, wait for natural end (or a long idle).\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = setTimeout(finish, Math.max(idleMs * 20, 1000));\n\t\t};\n\t\tconst onEnd = () => finish();\n\t\tlet timer = setTimeout(finish, Number.isFinite(idleMs) ? Math.max(0, idleMs) : 50);\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.on(\"data\", onData);\n\t\tprocess.stdin.on(\"end\", onEnd);\n\t\tprocess.stdin.on(\"error\", onEnd);\n\t\tprocess.stdin.resume();\n\t});\n}\n\nfunction collectSettingsDiagnostics(\n\tsettingsManager: SettingsManager,\n\tcontext: string,\n): AgentSessionRuntimeDiagnostic[] {\n\treturn settingsManager.drainErrors().map(({ scope, error }) => ({\n\t\ttype: \"warning\",\n\t\tmessage: `(${context}, ${scope} settings) ${error.message}`,\n\t}));\n}\n\nfunction reportDiagnostics(diagnostics: readonly AgentSessionRuntimeDiagnostic[]): void {\n\tfor (const diagnostic of diagnostics) {\n\t\tconst color = diagnostic.type === \"error\" ? chalk.red : diagnostic.type === \"warning\" ? chalk.yellow : chalk.dim;\n\t\tconst prefix = diagnostic.type === \"error\" ? \"Error: \" : diagnostic.type === \"warning\" ? \"Warning: \" : \"\";\n\t\tconsole.error(color(`${prefix}${diagnostic.message}`));\n\t}\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ntype AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nfunction hasPromptIntent(parsed: Args): boolean {\n\treturn (\n\t\tparsed.print === true ||\n\t\tparsed.messages.length > 0 ||\n\t\tparsed.fileArgs.length > 0 ||\n\t\tparsed.mode === \"json\" ||\n\t\tparsed.mode === \"rpc\"\n\t);\n}\n\n/** True when a controlling terminal is available (stdin/out/err or /dev/tty). */\nfunction _hasControllingTerminal(): boolean {\n\tif (process.stdin.isTTY || process.stdout.isTTY || process.stderr.isTTY) return true;\n\ttry {\n\t\tconst fd = fs.openSync(\"/dev/tty\", \"r+\");\n\t\tfs.closeSync(fd);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * When stdin/stdout were detached from the TTY (some IDE hosts / wrappers),\n * rebind them to /dev/tty so InteractiveMode/ProcessTerminal can run.\n * No-op if already a TTY or /dev/tty is unavailable.\n */\nfunction attachStdioToControllingTty(): boolean {\n\tif (process.stdin.isTTY && process.stdout.isTTY) return true;\n\ttry {\n\t\t// Prefer reopening stdio FDs against the controlling terminal.\n\t\t// Socket-over-/dev/tty is enough for isTTY detection + raw mode shim.\n\t\tconst fd = fs.openSync(\"/dev/tty\", \"r+\");\n\t\tconst sock = new net.Socket({ fd, readable: true, writable: true });\n\t\tObject.defineProperty(sock, \"isTTY\", { value: true, configurable: true });\n\t\tObject.defineProperty(sock, \"isRaw\", { value: false, writable: true, configurable: true });\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t(sock as any).setRawMode = (mode: boolean) => {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t(sock as any).isRaw = !!mode;\n\t\t\treturn sock;\n\t\t};\n\t\tif (!process.stdin.isTTY) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t(process as any).stdin = sock;\n\t\t}\n\t\tif (!process.stdout.isTTY) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t(process as any).stdout = sock;\n\t\t}\n\t\treturn !!(process.stdin.isTTY && process.stdout.isTTY);\n\t} catch {\n\t\treturn !!(process.stdin.isTTY && process.stdout.isTTY);\n\t}\n}\n\nfunction resolveAppMode(parsed: Args, stdinIsTTY: boolean, stdoutIsTTY: boolean = !!process.stdout.isTTY): AppMode {\n\tif (parsed.mode === \"rpc\") {\n\t\treturn \"rpc\";\n\t}\n\tif (parsed.mode === \"json\") {\n\t\treturn \"json\";\n\t}\n\t// Explicit single-shot (-p / --print)\n\tif (parsed.print) {\n\t\treturn \"print\";\n\t}\n\t// Bare `omk` (no prompt args) must open the TUI. Never fall into silent/empty print.\n\tif (!hasPromptIntent(parsed)) {\n\t\treturn \"interactive\";\n\t}\n\t// Piped automation: non-tty stdin with a prompt → print mode\n\tif (!stdinIsTTY || !stdoutIsTTY) {\n\t\treturn \"print\";\n\t}\n\treturn \"interactive\";\n}\n\nfunction toPrintOutputMode(appMode: AppMode): Exclude<Mode, \"rpc\"> {\n\treturn appMode === \"json\" ? \"json\" : \"text\";\n}\n\nasync function prepareInitialMessage(\n\tparsed: Args,\n\tautoResizeImages: boolean,\n\tstdinContent?: string,\n): Promise<{\n\tinitialMessage?: string;\n\tinitialImages?: ImageContent[];\n}> {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn buildInitialMessage({ parsed, stdinContent });\n\t}\n\n\tconst { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });\n\treturn buildInitialMessage({\n\t\tparsed,\n\t\tfileText: text,\n\t\tfileImages: images,\n\t\tstdinContent,\n\t});\n}\n\n/** Result from resolving a session argument */\ntype ResolvedSession =\n\t| { type: \"path\"; path: string } // Direct file path\n\t| { type: \"local\"; path: string } // Found in current project\n\t| { type: \"global\"; path: string; cwd: string } // Found in different project\n\t| { type: \"not_found\"; arg: string }; // Not found anywhere\n\n/**\n * Resolve a session argument to a file path.\n * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.\n */\nasync function findLocalSessionByExactId(\n\tsessionId: string,\n\tcwd: string,\n\tsessionDir?: string,\n): Promise<{ type: \"local\"; path: string } | undefined> {\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatch = localSessions.find((s) => s.id === sessionId);\n\treturn localMatch ? { type: \"local\", path: localMatch.path } : undefined;\n}\n\nasync function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {\n\t// If it looks like a file path, resolve it before handing it to the session manager.\n\tif (sessionArg.includes(\"/\") || sessionArg.includes(\"\\\\\") || sessionArg.endsWith(\".jsonl\")) {\n\t\treturn { type: \"path\", path: resolvePath(sessionArg, cwd) };\n\t}\n\n\t// Try to match as session ID in current project first\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatch =\n\t\tlocalSessions.find((s) => s.id === sessionArg) ?? localSessions.find((s) => s.id.startsWith(sessionArg));\n\n\tif (localMatch) {\n\t\treturn { type: \"local\", path: localMatch.path };\n\t}\n\n\t// Try global search across all projects\n\tconst allSessions = await SessionManager.listAll(sessionDir);\n\tconst globalMatch =\n\t\tallSessions.find((s) => s.id === sessionArg) ?? allSessions.find((s) => s.id.startsWith(sessionArg));\n\n\tif (globalMatch) {\n\t\treturn { type: \"global\", path: globalMatch.path, cwd: globalMatch.cwd };\n\t}\n\n\t// Not found anywhere\n\treturn { type: \"not_found\", arg: sessionArg };\n}\n\n/** Prompt user for yes/no confirmation */\nasync function promptConfirm(message: string): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createInterface({\n\t\t\tinput: process.stdin,\n\t\t\toutput: process.stdout,\n\t\t});\n\t\trl.question(`${message} [y/N] `, (answer) => {\n\t\t\trl.close();\n\t\t\tresolve(answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\");\n\t\t});\n\t});\n}\n\nfunction validateForkFlags(parsed: Args): void {\n\tif (!parsed.fork) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tconsole.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction validateSessionIdFlags(parsed: Args): void {\n\tif (parsed.sessionId === undefined) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tconsole.error(chalk.red(`Error: --session-id cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n\n\ttry {\n\t\tassertValidSessionId(parsed.sessionId);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string, sessionId?: string): SessionManager {\n\ttry {\n\t\treturn SessionManager.forkFrom(sourcePath, cwd, sessionDir, { id: sessionId });\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nasync function createSessionManager(\n\tparsed: Args,\n\tcwd: string,\n\tsessionDir: string | undefined,\n\tsettingsManager: SettingsManager,\n): Promise<SessionManager> {\n\tif (parsed.noSession || parsed.help || parsed.listModels !== undefined) {\n\t\treturn SessionManager.inMemory(cwd);\n\t}\n\n\tif (parsed.fork) {\n\t\tif (parsed.sessionId) {\n\t\t\tconst existingTarget = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);\n\t\t\tif (existingTarget) {\n\t\t\t\tconsole.error(chalk.red(`Session already exists with id '${parsed.sessionId}'`));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\n\t\tconst resolved = await resolveSessionPath(parsed.fork, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\tcase \"global\":\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir, parsed.sessionId);\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.session) {\n\t\tconst resolved = await resolveSessionPath(parsed.session, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\t\treturn SessionManager.open(resolved.path, sessionDir);\n\n\t\t\tcase \"global\": {\n\t\t\t\tconsole.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));\n\t\t\t\tconst shouldFork = await promptConfirm(\"Fork this session into current directory?\");\n\t\t\t\tif (!shouldFork) {\n\t\t\t\t\tconsole.log(chalk.dim(\"Aborted.\"));\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t}\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\t\t\t}\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.resume) {\n\t\tinitTheme(settingsManager.getTheme(), true);\n\t\ttry {\n\t\t\tconst selectedPath = await selectSession(\n\t\t\t\t(onProgress) => SessionManager.list(cwd, sessionDir, onProgress),\n\t\t\t\t(onProgress) => SessionManager.listAll(sessionDir, onProgress),\n\t\t\t);\n\t\t\tif (!selectedPath) {\n\t\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\treturn SessionManager.open(selectedPath, sessionDir);\n\t\t} finally {\n\t\t\tstopThemeWatcher();\n\t\t}\n\t}\n\n\tif (parsed.continue) {\n\t\treturn SessionManager.continueRecent(cwd, sessionDir);\n\t}\n\n\tif (parsed.sessionId) {\n\t\tconst existingSession = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);\n\t\tif (existingSession) {\n\t\t\treturn SessionManager.open(existingSession.path, sessionDir);\n\t\t}\n\t}\n\n\treturn SessionManager.create(cwd, sessionDir, { id: parsed.sessionId });\n}\n\nfunction buildSessionOptions(\n\tparsed: Args,\n\tscopedModels: ScopedModel[],\n\thasExistingSession: boolean,\n\tmodelRegistry: ModelRegistry,\n\tsettingsManager: SettingsManager,\n): {\n\toptions: CreateAgentSessionOptions;\n\tcliThinkingFromModel: boolean;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n} {\n\tconst options: CreateAgentSessionOptions = {};\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tlet cliThinkingFromModel = false;\n\n\t// Model from CLI\n\t// - supports --provider <name> --model <pattern>\n\t// - supports --model <provider>/<pattern>\n\tif (parsed.model) {\n\t\tconst resolved = resolveCliModel({\n\t\t\tcliProvider: parsed.provider,\n\t\t\tcliModel: parsed.model,\n\t\t\tmodelRegistry,\n\t\t});\n\t\tif (resolved.warning) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: resolved.warning });\n\t\t}\n\t\tif (resolved.error) {\n\t\t\tdiagnostics.push({ type: \"error\", message: resolved.error });\n\t\t}\n\t\tif (resolved.model) {\n\t\t\toptions.model = resolved.model;\n\t\t\toptions.modelPinned = true;\n\t\t\t// Allow \"--model <pattern>:<thinking>\" as a shorthand.\n\t\t\t// Explicit --thinking still takes precedence (applied later).\n\t\t\tif (!parsed.thinking && resolved.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = resolved.thinkingLevel;\n\t\t\t\tcliThinkingFromModel = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!options.model && scopedModels.length > 0 && !hasExistingSession) {\n\t\t// Check if saved default is in scoped models - use it if so, otherwise first scoped model\n\t\tconst savedProvider = settingsManager.getDefaultProvider();\n\t\tconst savedModelId = settingsManager.getDefaultModel();\n\t\tconst savedModel = savedProvider && savedModelId ? modelRegistry.find(savedProvider, savedModelId) : undefined;\n\t\tconst savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;\n\n\t\tif (savedInScope) {\n\t\t\toptions.model = savedInScope.model;\n\t\t\t// Use thinking level from scoped model config if explicitly set\n\t\t\tif (!parsed.thinking && savedInScope.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = savedInScope.thinkingLevel;\n\t\t\t}\n\t\t} else {\n\t\t\toptions.model = scopedModels[0].model;\n\t\t\t// Use thinking level from first scoped model if explicitly set\n\t\t\tif (!parsed.thinking && scopedModels[0].thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = scopedModels[0].thinkingLevel;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Thinking level from CLI (takes precedence over scoped model thinking levels set above)\n\tif (parsed.thinking) {\n\t\toptions.thinkingLevel = parsed.thinking;\n\t}\n\n\t// Scoped models for Ctrl+P cycling\n\t// Keep thinking level undefined when not explicitly set in the model pattern.\n\t// Undefined means \"inherit current session thinking level\" during cycling.\n\tif (scopedModels.length > 0) {\n\t\toptions.scopedModels = scopedModels.map((sm) => ({\n\t\t\tmodel: sm.model,\n\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t}));\n\t}\n\n\t// API key from CLI - set in authStorage\n\t// (handled by caller before createAgentSession)\n\n\t// Tools\n\tif (parsed.noTools) {\n\t\toptions.noTools = \"all\";\n\t} else if (parsed.noBuiltinTools) {\n\t\toptions.noTools = \"builtin\";\n\t}\n\tif (parsed.tools) {\n\t\toptions.tools = [...parsed.tools];\n\t}\n\tif (parsed.excludeTools) {\n\t\toptions.excludeTools = [...parsed.excludeTools];\n\t}\n\n\treturn { options, cliThinkingFromModel, diagnostics };\n}\n\nfunction resolveCliPaths(cwd: string, paths: string[] | undefined): string[] | undefined {\n\treturn paths?.map((value) => (isLocalPath(value) ? resolvePath(value, cwd) : value));\n}\n\nasync function promptForMissingSessionCwd(\n\tissue: SessionCwdIssue,\n\tsettingsManager: SettingsManager,\n): Promise<string | undefined> {\n\tinitTheme(settingsManager.getTheme());\n\tsetKeybindings(KeybindingsManager.create());\n\n\treturn new Promise((resolve) => {\n\t\tconst ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());\n\t\tui.setClearOnShrink(settingsManager.getClearOnShrink());\n\n\t\tlet settled = false;\n\t\tconst finish = (result: string | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\tformatMissingSessionCwdPrompt(issue),\n\t\t\t[\"Continue\", \"Cancel\"],\n\t\t\t(option) => finish(option === \"Continue\" ? issue.fallbackCwd : undefined),\n\t\t\t() => finish(undefined),\n\t\t\t{ tui: ui },\n\t\t);\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector);\n\t\tui.start();\n\t});\n}\n\nexport interface MainOptions {\n\textensionFactories?: ExtensionFactory[];\n}\n\nexport async function main(args: string[], options?: MainOptions) {\n\tresetTimings();\n\tconst offlineMode = args.includes(\"--offline\") || isTruthyEnvFlag(process.env.OMK_OFFLINE);\n\tif (offlineMode) {\n\t\tprocess.env.OMK_OFFLINE = \"1\";\n\t\tprocess.env.OMK_SKIP_VERSION_CHECK = \"1\";\n\t}\n\n\tif (process.platform === \"win32\") {\n\t\tcleanupWindowsSelfUpdateQuarantine(getPackageDir());\n\t}\n\n\tconst packageDoctor = await runPackageDoctorCli(args);\n\tif (packageDoctor.handled) {\n\t\tprocess.exitCode = packageDoctor.exitCode;\n\t\treturn;\n\t}\n\n\tif (await handlePackageCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleConfigCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleCodexBarQuotaCommand(args)) {\n\t\treturn;\n\t}\n\n\tconst sessionDoctor = await runSessionDoctorCli(args);\n\tif (sessionDoctor.handled) {\n\t\tprocess.exitCode = sessionDoctor.exitCode;\n\t\treturn;\n\t}\n\n\tconst doctorProvider = await runDoctorProviderCli(args);\n\tif (doctorProvider.handled) {\n\t\tprocess.exitCode = doctorProvider.exitCode;\n\t\treturn;\n\t}\n\n\tconst resourceDoctor = await runResourceDoctorCli(args);\n\tif (resourceDoctor.handled) {\n\t\tprocess.exitCode = resourceDoctor.exitCode;\n\t\treturn;\n\t}\n\n\tconst stats = runStatsCli(args);\n\tif (stats.handled) {\n\t\tprocess.exitCode = stats.exitCode;\n\t\treturn;\n\t}\n\n\tconst sdkSession = await runSdkSessionCli(args);\n\tif (sdkSession.handled) {\n\t\tprocess.exitCode = sdkSession.exitCode;\n\t\treturn;\n\t}\n\n\tconst routerFeedback = runRouterFeedbackCli(args);\n\tif (routerFeedback.handled) {\n\t\tprocess.exitCode = routerFeedback.exitCode;\n\t\treturn;\n\t}\n\n\tconst parsed = parseArgs(args);\n\tif (parsed.diagnostics.length > 0) {\n\t\tfor (const d of parsed.diagnostics) {\n\t\t\tconst color = d.type === \"error\" ? chalk.red : chalk.yellow;\n\t\t\tconsole.error(color(`${d.type === \"error\" ? \"Error\" : \"Warning\"}: ${d.message}`));\n\t\t}\n\t\tif (parsed.diagnostics.some((d) => d.type === \"error\")) {\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"parseArgs\");\n\tlet appMode = resolveAppMode(parsed, !!process.stdin.isTTY, !!process.stdout.isTTY);\n\t// Bare interactive launch: try to reclaim the controlling TTY when a host\n\t// detached stdio. Launcher may already have re-exec'd under `script`.\n\tif (appMode === \"interactive\" && !hasPromptIntent(parsed)) {\n\t\tif (!process.stdin.isTTY || !process.stdout.isTTY) {\n\t\t\tattachStdioToControllingTty();\n\t\t}\n\t\t// Do not hard-exit here — ProcessTerminal still works on many WSL/Windows\n\t\t// hosts even when Node's isTTY is false after a PTY re-exec race.\n\t}\n\tconst shouldTakeOverStdout = appMode !== \"interactive\";\n\tif (shouldTakeOverStdout) {\n\t\ttakeOverStdout();\n\t}\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.export) {\n\t\tlet result: string;\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tresult = await exportFromFile(parsed.export, outputPath);\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconsole.log(`Exported to: ${result}`);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tvalidateForkFlags(parsed);\n\tvalidateSessionIdFlags(parsed);\n\n\t// Run migrations (pass cwd for project-local migrations)\n\tconst { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());\n\ttime(\"runMigrations\");\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst startupSettingsManager = SettingsManager.create(cwd, agentDir);\n\treportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, \"startup session lookup\"));\n\n\t// Decide the final runtime cwd before creating cwd-bound runtime services.\n\t// --session and --resume may select a session from another project, so project-local\n\t// settings, resources, provider registrations, and models must be resolved only after\n\t// the target session cwd is known. The startup-cwd settings manager is used only for\n\t// sessionDir lookup during session selection.\n\tconst envSessionDir = process.env[ENV_SESSION_DIR];\n\tconst sessionDir =\n\t\t(parsed.sessionDir ? normalizePath(parsed.sessionDir) : undefined) ??\n\t\t(envSessionDir ? expandTildePath(envSessionDir) : undefined) ??\n\t\tstartupSettingsManager.getSessionDir();\n\tlet sessionManager = await createSessionManager(parsed, cwd, sessionDir, startupSettingsManager);\n\tconst missingSessionCwdIssue = getMissingSessionCwdIssue(sessionManager, cwd);\n\tif (missingSessionCwdIssue) {\n\t\tif (appMode === \"interactive\") {\n\t\t\tconst selectedCwd = await promptForMissingSessionCwd(missingSessionCwdIssue, startupSettingsManager);\n\t\t\tif (!selectedCwd) {\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\tsessionManager = SessionManager.open(missingSessionCwdIssue.sessionFile!, sessionDir, selectedCwd);\n\t\t} else {\n\t\t\tconsole.error(chalk.red(new MissingSessionCwdError(missingSessionCwdIssue).message));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\tif (parsed.name !== undefined) {\n\t\tconst name = parsed.name.trim();\n\t\tif (!name) {\n\t\t\tconsole.error(chalk.red(\"Error: --name requires a non-empty value\"));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tsessionManager.appendSessionInfo(name);\n\t}\n\ttime(\"createSessionManager\");\n\n\tconst resolvedExtensionPaths = resolveCliPaths(cwd, parsed.extensions);\n\tconst resolvedSkillPaths = resolveCliPaths(cwd, parsed.skills);\n\tconst resolvedPromptTemplatePaths = resolveCliPaths(cwd, parsed.promptTemplates);\n\tconst resolvedThemePaths = resolveCliPaths(cwd, parsed.themes);\n\tconst authStorage = AuthStorage.create();\n\tconst createRuntime: CreateAgentSessionRuntimeFactory = async ({\n\t\tcwd,\n\t\tagentDir,\n\t\tsessionManager,\n\t\tsessionStartEvent,\n\t}) => {\n\t\tconst services = await createAgentSessionServices({\n\t\t\tcwd,\n\t\t\tagentDir,\n\t\t\tauthStorage,\n\t\t\textensionFlagValues: parsed.unknownFlags,\n\t\t\tresourceLoaderOptions: {\n\t\t\t\tadditionalExtensionPaths: resolvedExtensionPaths,\n\t\t\t\tadditionalSkillPaths: resolvedSkillPaths,\n\t\t\t\tadditionalPromptTemplatePaths: resolvedPromptTemplatePaths,\n\t\t\t\tadditionalThemePaths: resolvedThemePaths,\n\t\t\t\tnoExtensions: parsed.noExtensions,\n\t\t\t\tnoSkills: parsed.noSkills,\n\t\t\t\tnoPromptTemplates: parsed.noPromptTemplates,\n\t\t\t\tnoThemes: parsed.noThemes,\n\t\t\t\tnoContextFiles: parsed.noContextFiles,\n\t\t\t\tsystemPrompt: parsed.systemPrompt,\n\t\t\t\tappendSystemPrompt: parsed.appendSystemPrompt,\n\t\t\t\textensionFactories: options?.extensionFactories,\n\t\t\t},\n\t\t});\n\t\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\t\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [\n\t\t\t...services.diagnostics,\n\t\t\t...collectSettingsDiagnostics(settingsManager, \"runtime creation\"),\n\t\t\t...resourceLoader.getExtensions().errors.map(({ path, error }) => ({\n\t\t\t\ttype: \"error\" as const,\n\t\t\t\tmessage: `Failed to load extension \"${path}\": ${error}`,\n\t\t\t})),\n\t\t];\n\n\t\tconst modelPatterns = parsed.models ?? settingsManager.getEnabledModels();\n\t\tconst scopedModels =\n\t\t\tmodelPatterns && modelPatterns.length > 0 ? await resolveModelScope(modelPatterns, modelRegistry) : [];\n\t\tconst {\n\t\t\toptions: sessionOptions,\n\t\t\tcliThinkingFromModel,\n\t\t\tdiagnostics: sessionOptionDiagnostics,\n\t\t} = buildSessionOptions(\n\t\t\tparsed,\n\t\t\tscopedModels,\n\t\t\tsessionManager.buildSessionContext().messages.length > 0,\n\t\t\tmodelRegistry,\n\t\t\tsettingsManager,\n\t\t);\n\t\tdiagnostics.push(...sessionOptionDiagnostics);\n\n\t\tif (parsed.apiKey) {\n\t\t\tif (!sessionOptions.model) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tmessage: \"--api-key requires a model to be specified via --model, --provider/--model, or --models\",\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tauthStorage.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);\n\t\t\t}\n\t\t}\n\n\t\tconst created = await createAgentSessionFromServices({\n\t\t\tservices,\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent,\n\t\t\tmodel: sessionOptions.model,\n\t\t\tthinkingLevel: sessionOptions.thinkingLevel,\n\t\t\tscopedModels: sessionOptions.scopedModels,\n\t\t\tmodelPinned: sessionOptions.modelPinned,\n\t\t\ttools: sessionOptions.tools,\n\t\t\texcludeTools: sessionOptions.excludeTools,\n\t\t\tnoTools: sessionOptions.noTools,\n\t\t\tcustomTools: sessionOptions.customTools,\n\t\t});\n\t\tconst cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;\n\t\tif (created.session.model && cliThinkingOverride) {\n\t\t\tcreated.session.setThinkingLevel(created.session.thinkingLevel);\n\t\t}\n\n\t\treturn {\n\t\t\t...created,\n\t\t\tservices,\n\t\t\tdiagnostics,\n\t\t};\n\t};\n\ttime(\"createRuntime\");\n\tconst runtime = await createAgentSessionRuntime(createRuntime, {\n\t\tcwd: sessionManager.getCwd(),\n\t\tagentDir,\n\t\tsessionManager,\n\t});\n\ttime(\"createAgentSessionRuntime\");\n\tconst { services, session, modelFallbackMessage } = runtime;\n\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\tconfigureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());\n\n\tif (parsed.help) {\n\t\tconst extensionFlags = resourceLoader\n\t\t\t.getExtensions()\n\t\t\t.extensions.flatMap((extension) => Array.from(extension.flags.values()));\n\t\tprintHelp(extensionFlags);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(modelRegistry, searchPattern);\n\t\tprocess.exit(0);\n\t}\n\n\t// Read piped stdin content (if any) - skip for RPC mode which uses stdin for JSON-RPC.\n\t// Also skip on bare interactive launches so a stuck/non-tty stdin cannot force print mode.\n\tlet stdinContent: string | undefined;\n\tif (appMode !== \"rpc\" && !(appMode === \"interactive\" && !hasPromptIntent(parsed))) {\n\t\tstdinContent = await readPipedStdin();\n\t\tif (stdinContent !== undefined && appMode === \"interactive\") {\n\t\t\tappMode = \"print\";\n\t\t}\n\t}\n\ttime(\"readPipedStdin\");\n\n\tconst { initialMessage, initialImages } = await prepareInitialMessage(\n\t\tparsed,\n\t\tsettingsManager.getImageAutoResize(),\n\t\tstdinContent,\n\t);\n\ttime(\"prepareInitialMessage\");\n\tinitTheme(settingsManager.getTheme(), appMode === \"interactive\");\n\ttime(\"initTheme\");\n\n\t// Show deprecation warnings in interactive mode\n\tif (appMode === \"interactive\" && deprecationWarnings.length > 0) {\n\t\tawait showDeprecationWarnings(deprecationWarnings);\n\t}\n\n\ttime(\"resolveModelScope\");\n\treportDiagnostics(runtime.diagnostics);\n\tif (runtime.diagnostics.some((diagnostic) => diagnostic.type === \"error\")) {\n\t\tprocess.exit(1);\n\t}\n\ttime(\"createAgentSession\");\n\n\tif (appMode !== \"interactive\" && !session.model) {\n\t\tconsole.error(chalk.red(formatNoModelsAvailableMessage()));\n\t\tprocess.exit(1);\n\t}\n\n\tconst startupBenchmark = isTruthyEnvFlag(process.env.OMK_STARTUP_BENCHMARK);\n\tif (startupBenchmark && appMode !== \"interactive\") {\n\t\tconsole.error(chalk.red(\"Error: OMK_STARTUP_BENCHMARK only supports interactive mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tif (appMode === \"rpc\") {\n\t\tprintTimings();\n\t\tawait runRpcMode(runtime);\n\t} else if (appMode === \"interactive\") {\n\t\tconst interactiveMode = new InteractiveMode(runtime, {\n\t\t\tmigratedProviders,\n\t\t\tmodelFallbackMessage,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\tinitialMessages: parsed.messages,\n\t\t\tverbose: parsed.verbose,\n\t\t});\n\t\tif (startupBenchmark) {\n\t\t\tawait interactiveMode.init();\n\t\t\ttime(\"interactiveMode.init\");\n\t\t\tprintTimings();\n\t\t\tinteractiveMode.stop();\n\t\t\tstopThemeWatcher();\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tprintTimings();\n\t\tawait interactiveMode.run();\n\t} else {\n\t\tprintTimings();\n\t\tconst exitCode = await runPrintMode(runtime, {\n\t\t\tmode: toPrintOutputMode(appMode),\n\t\t\tmessages: parsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t});\n\t\tstopThemeWatcher();\n\t\trestoreStdout();\n\t\tif (exitCode !== 0) {\n\t\t\tprocess.exitCode = exitCode;\n\t\t}\n\t\treturn;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,EAAE,6BAA6B,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAqBzF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAwBnE,OAAO,EAAE,6BAA6B,EAAE,eAAe,EAAE,CAAC;AAmf1D,MAAM,WAAW,WAAW;IAC3B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACxC;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,iBAqW/D","sourcesContent":["/**\n * Main entry point for the coding agent CLI.\n *\n * This file handles CLI argument parsing and translates them into\n * createAgentSession() options. The SDK does the heavy lifting.\n */\n\nimport * as fs from \"node:fs\";\nimport * as net from \"node:net\";\nimport { createInterface } from \"node:readline\";\nimport chalk from \"chalk\";\nimport { type ImageContent, modelsAreEqual } from \"omk-ai\";\nimport { ProcessTerminal, setKeybindings, TUI } from \"omk-tui\";\nimport { type Args, type Mode, parseArgs, printHelp } from \"./cli/args.ts\";\nimport { processFileArguments } from \"./cli/file-processor.ts\";\nimport { buildInitialMessage } from \"./cli/initial-message.ts\";\nimport { listModels } from \"./cli/list-models.ts\";\nimport { isExplicitExtensionDiagnostic, resolveCliPaths } from \"./cli/resource-paths.ts\";\nimport { selectSession } from \"./cli/session-picker.ts\";\nimport { handleCodexBarQuotaCommand } from \"./codexbar-cli.ts\";\nimport { runAdaptOrchDoctorCli } from \"./commands/adaptorch-doctor-cli.ts\";\nimport { runDoctorProviderCli } from \"./commands/doctor-provider-cli.ts\";\nimport { runPackageDoctorCli } from \"./commands/package-doctor-cli.ts\";\nimport { runResourceDoctorCli } from \"./commands/resource-doctor-cli.ts\";\nimport { runRouterFeedbackCli } from \"./commands/router-feedback-cli.ts\";\nimport { runSdkSessionCli } from \"./commands/sdk-session-cli.ts\";\nimport { runSessionDoctorCli } from \"./commands/session-doctor-cli.ts\";\nimport { runStatsCli } from \"./commands/stats-cli.ts\";\nimport { ENV_SESSION_DIR, expandTildePath, getAgentDir, getPackageDir, VERSION } from \"./config.ts\";\nimport { type CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from \"./core/agent-session-runtime.ts\";\nimport {\n\ttype AgentSessionRuntimeDiagnostic,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./core/agent-session-services.ts\";\nimport { formatNoModelsAvailableMessage } from \"./core/auth-guidance.ts\";\nimport { AuthStorage } from \"./core/auth-storage.ts\";\nimport { exportFromFile } from \"./core/export-html/index.ts\";\nimport type { ExtensionFactory } from \"./core/extensions/types.ts\";\nimport { configureHttpDispatcher } from \"./core/http-dispatcher.ts\";\nimport { KeybindingsManager } from \"./core/keybindings.ts\";\nimport type { ModelRegistry } from \"./core/model-registry.ts\";\nimport { resolveCliModel, resolveModelScope, type ScopedModel } from \"./core/model-resolver.ts\";\nimport { restoreStdout, takeOverStdout } from \"./core/output-guard.ts\";\nimport type { CreateAgentSessionOptions } from \"./core/sdk.ts\";\nimport {\n\tformatMissingSessionCwdPrompt,\n\tgetMissingSessionCwdIssue,\n\tMissingSessionCwdError,\n\ttype SessionCwdIssue,\n} from \"./core/session-cwd.ts\";\nimport { assertValidSessionId, SessionManager } from \"./core/session-manager.ts\";\nimport { SettingsManager } from \"./core/settings-manager.ts\";\nimport { printTimings, resetTimings, time } from \"./core/timings.ts\";\nimport { runMigrations, showDeprecationWarnings } from \"./migrations.ts\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.ts\";\nimport { ExtensionSelectorComponent } from \"./modes/interactive/components/extension-selector.ts\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.ts\";\nimport { handleConfigCommand, handlePackageCommand } from \"./package-manager-cli.ts\";\nimport { normalizePath, resolvePath } from \"./utils/paths.ts\";\nimport { cleanupWindowsSelfUpdateQuarantine } from \"./utils/windows-self-update.ts\";\n\nexport { isExplicitExtensionDiagnostic, resolveCliPaths };\n\n/**\n * Read all content from piped stdin.\n * Returns undefined if stdin is a TTY (interactive terminal).\n *\n * Non-TTY stdin that never ends (some IDE/terminal hosts keep fd0 open) used to\n * hang forever here — that looked like \"omk\" with no response. Bound the wait:\n * if no bytes arrive quickly and nothing is buffered, treat as empty.\n */\nasync function readPipedStdin(): Promise<string | undefined> {\n\t// If stdin is a TTY, we're running interactively - don't read stdin\n\tif (process.stdin.isTTY) {\n\t\treturn undefined;\n\t}\n\n\t// Already drained\n\tif (process.stdin.readableEnded) {\n\t\treturn undefined;\n\t}\n\n\tconst idleMs = Number(process.env.OMK_STDIN_IDLE_MS || 50);\n\treturn new Promise((resolve) => {\n\t\tlet data = \"\";\n\t\tlet settled = false;\n\t\tconst finish = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tprocess.stdin.off(\"data\", onData);\n\t\t\tprocess.stdin.off(\"end\", onEnd);\n\t\t\tprocess.stdin.off(\"error\", onEnd);\n\t\t\tresolve(data.trim() || undefined);\n\t\t};\n\t\tconst onData = (chunk: string | Buffer) => {\n\t\t\tdata += typeof chunk === \"string\" ? chunk : chunk.toString(\"utf8\");\n\t\t\t// Once data starts flowing, wait for natural end (or a long idle).\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = setTimeout(finish, Math.max(idleMs * 20, 1000));\n\t\t};\n\t\tconst onEnd = () => finish();\n\t\tlet timer = setTimeout(finish, Number.isFinite(idleMs) ? Math.max(0, idleMs) : 50);\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.on(\"data\", onData);\n\t\tprocess.stdin.on(\"end\", onEnd);\n\t\tprocess.stdin.on(\"error\", onEnd);\n\t\tprocess.stdin.resume();\n\t});\n}\n\nfunction collectSettingsDiagnostics(\n\tsettingsManager: SettingsManager,\n\tcontext: string,\n): AgentSessionRuntimeDiagnostic[] {\n\treturn settingsManager.drainErrors().map(({ scope, error }) => ({\n\t\ttype: \"warning\",\n\t\tmessage: `(${context}, ${scope} settings) ${error.message}`,\n\t}));\n}\n\nfunction reportDiagnostics(diagnostics: readonly AgentSessionRuntimeDiagnostic[]): void {\n\tfor (const diagnostic of diagnostics) {\n\t\tconst color = diagnostic.type === \"error\" ? chalk.red : diagnostic.type === \"warning\" ? chalk.yellow : chalk.dim;\n\t\tconst prefix = diagnostic.type === \"error\" ? \"Error: \" : diagnostic.type === \"warning\" ? \"Warning: \" : \"\";\n\t\tconsole.error(color(`${prefix}${diagnostic.message}`));\n\t}\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ntype AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nfunction hasPromptIntent(parsed: Args): boolean {\n\treturn (\n\t\tparsed.print === true ||\n\t\tparsed.messages.length > 0 ||\n\t\tparsed.fileArgs.length > 0 ||\n\t\tparsed.mode === \"json\" ||\n\t\tparsed.mode === \"rpc\"\n\t);\n}\n\n/**\n * When stdin/stdout were detached from the TTY (some IDE hosts / wrappers),\n * rebind them to /dev/tty so InteractiveMode/ProcessTerminal can run.\n * No-op if already a TTY or /dev/tty is unavailable.\n */\nfunction attachStdioToControllingTty(): boolean {\n\tif (process.stdin.isTTY && process.stdout.isTTY) return true;\n\ttry {\n\t\t// Prefer reopening stdio FDs against the controlling terminal.\n\t\t// Socket-over-/dev/tty is enough for isTTY detection + raw mode shim.\n\t\tconst fd = fs.openSync(\"/dev/tty\", \"r+\");\n\t\tconst sock = new net.Socket({ fd, readable: true, writable: true });\n\t\tObject.defineProperty(sock, \"isTTY\", { value: true, configurable: true });\n\t\tObject.defineProperty(sock, \"isRaw\", { value: false, writable: true, configurable: true });\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t(sock as any).setRawMode = (mode: boolean) => {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t(sock as any).isRaw = !!mode;\n\t\t\treturn sock;\n\t\t};\n\t\tif (!process.stdin.isTTY) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t(process as any).stdin = sock;\n\t\t}\n\t\tif (!process.stdout.isTTY) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t(process as any).stdout = sock;\n\t\t}\n\t\treturn !!(process.stdin.isTTY && process.stdout.isTTY);\n\t} catch {\n\t\treturn !!(process.stdin.isTTY && process.stdout.isTTY);\n\t}\n}\n\nfunction resolveAppMode(parsed: Args, stdinIsTTY: boolean, stdoutIsTTY: boolean = !!process.stdout.isTTY): AppMode {\n\tif (parsed.mode === \"rpc\") {\n\t\treturn \"rpc\";\n\t}\n\tif (parsed.mode === \"json\") {\n\t\treturn \"json\";\n\t}\n\t// Explicit single-shot (-p / --print)\n\tif (parsed.print) {\n\t\treturn \"print\";\n\t}\n\t// Bare `omk` (no prompt args) must open the TUI. Never fall into silent/empty print.\n\tif (!hasPromptIntent(parsed)) {\n\t\treturn \"interactive\";\n\t}\n\t// Piped automation: non-tty stdin with a prompt → print mode\n\tif (!stdinIsTTY || !stdoutIsTTY) {\n\t\treturn \"print\";\n\t}\n\treturn \"interactive\";\n}\n\nfunction toPrintOutputMode(appMode: AppMode): Exclude<Mode, \"rpc\"> {\n\treturn appMode === \"json\" ? \"json\" : \"text\";\n}\n\nasync function prepareInitialMessage(\n\tparsed: Args,\n\tautoResizeImages: boolean,\n\tstdinContent?: string,\n): Promise<{\n\tinitialMessage?: string;\n\tinitialImages?: ImageContent[];\n}> {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn buildInitialMessage({ parsed, stdinContent });\n\t}\n\n\tconst { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });\n\treturn buildInitialMessage({\n\t\tparsed,\n\t\tfileText: text,\n\t\tfileImages: images,\n\t\tstdinContent,\n\t});\n}\n\n/** Result from resolving a session argument */\ntype ResolvedSession =\n\t| { type: \"path\"; path: string } // Direct file path\n\t| { type: \"local\"; path: string } // Found in current project\n\t| { type: \"global\"; path: string; cwd: string } // Found in different project\n\t| { type: \"not_found\"; arg: string }; // Not found anywhere\n\n/**\n * Resolve a session argument to a file path.\n * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.\n */\nasync function findLocalSessionByExactId(\n\tsessionId: string,\n\tcwd: string,\n\tsessionDir?: string,\n): Promise<{ type: \"local\"; path: string } | undefined> {\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatch = localSessions.find((s) => s.id === sessionId);\n\treturn localMatch ? { type: \"local\", path: localMatch.path } : undefined;\n}\n\nasync function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {\n\t// If it looks like a file path, resolve it before handing it to the session manager.\n\tif (sessionArg.includes(\"/\") || sessionArg.includes(\"\\\\\") || sessionArg.endsWith(\".jsonl\")) {\n\t\treturn { type: \"path\", path: resolvePath(sessionArg, cwd) };\n\t}\n\n\t// Try to match as session ID in current project first\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatch =\n\t\tlocalSessions.find((s) => s.id === sessionArg) ?? localSessions.find((s) => s.id.startsWith(sessionArg));\n\n\tif (localMatch) {\n\t\treturn { type: \"local\", path: localMatch.path };\n\t}\n\n\t// Try global search across all projects\n\tconst allSessions = await SessionManager.listAll(sessionDir);\n\tconst globalMatch =\n\t\tallSessions.find((s) => s.id === sessionArg) ?? allSessions.find((s) => s.id.startsWith(sessionArg));\n\n\tif (globalMatch) {\n\t\treturn { type: \"global\", path: globalMatch.path, cwd: globalMatch.cwd };\n\t}\n\n\t// Not found anywhere\n\treturn { type: \"not_found\", arg: sessionArg };\n}\n\n/** Prompt user for yes/no confirmation */\nasync function promptConfirm(message: string): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createInterface({\n\t\t\tinput: process.stdin,\n\t\t\toutput: process.stdout,\n\t\t});\n\t\trl.question(`${message} [y/N] `, (answer) => {\n\t\t\trl.close();\n\t\t\tresolve(answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\");\n\t\t});\n\t});\n}\n\nfunction validateForkFlags(parsed: Args): void {\n\tif (!parsed.fork) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tconsole.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction validateSessionIdFlags(parsed: Args): void {\n\tif (parsed.sessionId === undefined) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tconsole.error(chalk.red(`Error: --session-id cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n\n\ttry {\n\t\tassertValidSessionId(parsed.sessionId);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string, sessionId?: string): SessionManager {\n\ttry {\n\t\treturn SessionManager.forkFrom(sourcePath, cwd, sessionDir, { id: sessionId });\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nasync function createSessionManager(\n\tparsed: Args,\n\tcwd: string,\n\tsessionDir: string | undefined,\n\tsettingsManager: SettingsManager,\n): Promise<SessionManager> {\n\tif (parsed.noSession || parsed.help || parsed.listModels !== undefined) {\n\t\treturn SessionManager.inMemory(cwd);\n\t}\n\n\tif (parsed.fork) {\n\t\tif (parsed.sessionId) {\n\t\t\tconst existingTarget = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);\n\t\t\tif (existingTarget) {\n\t\t\t\tconsole.error(chalk.red(`Session already exists with id '${parsed.sessionId}'`));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\n\t\tconst resolved = await resolveSessionPath(parsed.fork, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\tcase \"global\":\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir, parsed.sessionId);\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.session) {\n\t\tconst resolved = await resolveSessionPath(parsed.session, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\t\treturn SessionManager.open(resolved.path, sessionDir);\n\n\t\t\tcase \"global\": {\n\t\t\t\tconsole.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));\n\t\t\t\tconst shouldFork = await promptConfirm(\"Fork this session into current directory?\");\n\t\t\t\tif (!shouldFork) {\n\t\t\t\t\tconsole.log(chalk.dim(\"Aborted.\"));\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t}\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\t\t\t}\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.resume) {\n\t\tinitTheme(settingsManager.getTheme(), true);\n\t\ttry {\n\t\t\tconst selectedPath = await selectSession(\n\t\t\t\t(onProgress) => SessionManager.list(cwd, sessionDir, onProgress),\n\t\t\t\t(onProgress) => SessionManager.listAll(sessionDir, onProgress),\n\t\t\t);\n\t\t\tif (!selectedPath) {\n\t\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\treturn SessionManager.open(selectedPath, sessionDir);\n\t\t} finally {\n\t\t\tstopThemeWatcher();\n\t\t}\n\t}\n\n\tif (parsed.continue) {\n\t\treturn SessionManager.continueRecent(cwd, sessionDir);\n\t}\n\n\tif (parsed.sessionId) {\n\t\tconst existingSession = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);\n\t\tif (existingSession) {\n\t\t\treturn SessionManager.open(existingSession.path, sessionDir);\n\t\t}\n\t}\n\n\treturn SessionManager.create(cwd, sessionDir, { id: parsed.sessionId });\n}\n\nfunction buildSessionOptions(\n\tparsed: Args,\n\tscopedModels: ScopedModel[],\n\thasExistingSession: boolean,\n\tmodelRegistry: ModelRegistry,\n\tsettingsManager: SettingsManager,\n): {\n\toptions: CreateAgentSessionOptions;\n\tcliThinkingFromModel: boolean;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n} {\n\tconst options: CreateAgentSessionOptions = {};\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tlet cliThinkingFromModel = false;\n\n\t// Model from CLI\n\t// - supports --provider <name> --model <pattern>\n\t// - supports --model <provider>/<pattern>\n\tif (parsed.model) {\n\t\tconst resolved = resolveCliModel({\n\t\t\tcliProvider: parsed.provider,\n\t\t\tcliModel: parsed.model,\n\t\t\tmodelRegistry,\n\t\t});\n\t\tif (resolved.warning) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: resolved.warning });\n\t\t}\n\t\tif (resolved.error) {\n\t\t\tdiagnostics.push({ type: \"error\", message: resolved.error });\n\t\t}\n\t\tif (resolved.model) {\n\t\t\toptions.model = resolved.model;\n\t\t\toptions.modelPinned = true;\n\t\t\t// Allow \"--model <pattern>:<thinking>\" as a shorthand.\n\t\t\t// Explicit --thinking still takes precedence (applied later).\n\t\t\tif (!parsed.thinking && resolved.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = resolved.thinkingLevel;\n\t\t\t\tcliThinkingFromModel = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!options.model && scopedModels.length > 0 && !hasExistingSession) {\n\t\t// Check if saved default is in scoped models - use it if so, otherwise first scoped model\n\t\tconst savedProvider = settingsManager.getDefaultProvider();\n\t\tconst savedModelId = settingsManager.getDefaultModel();\n\t\tconst savedModel = savedProvider && savedModelId ? modelRegistry.find(savedProvider, savedModelId) : undefined;\n\t\tconst savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;\n\n\t\tif (savedInScope) {\n\t\t\toptions.model = savedInScope.model;\n\t\t\t// Use thinking level from scoped model config if explicitly set\n\t\t\tif (!parsed.thinking && savedInScope.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = savedInScope.thinkingLevel;\n\t\t\t}\n\t\t} else {\n\t\t\toptions.model = scopedModels[0].model;\n\t\t\t// Use thinking level from first scoped model if explicitly set\n\t\t\tif (!parsed.thinking && scopedModels[0].thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = scopedModels[0].thinkingLevel;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Thinking level from CLI (takes precedence over scoped model thinking levels set above)\n\tif (parsed.thinking) {\n\t\toptions.thinkingLevel = parsed.thinking;\n\t}\n\n\t// Scoped models for Ctrl+P cycling\n\t// Keep thinking level undefined when not explicitly set in the model pattern.\n\t// Undefined means \"inherit current session thinking level\" during cycling.\n\tif (scopedModels.length > 0) {\n\t\toptions.scopedModels = scopedModels.map((sm) => ({\n\t\t\tmodel: sm.model,\n\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t}));\n\t}\n\n\t// API key from CLI - set in authStorage\n\t// (handled by caller before createAgentSession)\n\n\t// Tools\n\tif (parsed.noTools) {\n\t\toptions.noTools = \"all\";\n\t} else if (parsed.noBuiltinTools) {\n\t\toptions.noTools = \"builtin\";\n\t}\n\tif (parsed.tools) {\n\t\toptions.tools = [...parsed.tools];\n\t}\n\tif (parsed.excludeTools) {\n\t\toptions.excludeTools = [...parsed.excludeTools];\n\t}\n\n\treturn { options, cliThinkingFromModel, diagnostics };\n}\n\nasync function promptForMissingSessionCwd(\n\tissue: SessionCwdIssue,\n\tsettingsManager: SettingsManager,\n): Promise<string | undefined> {\n\tinitTheme(settingsManager.getTheme());\n\tsetKeybindings(KeybindingsManager.create());\n\n\treturn new Promise((resolve) => {\n\t\tconst ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());\n\t\tui.setClearOnShrink(settingsManager.getClearOnShrink());\n\n\t\tlet settled = false;\n\t\tconst finish = (result: string | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\tformatMissingSessionCwdPrompt(issue),\n\t\t\t[\"Continue\", \"Cancel\"],\n\t\t\t(option) => finish(option === \"Continue\" ? issue.fallbackCwd : undefined),\n\t\t\t() => finish(undefined),\n\t\t\t{ tui: ui },\n\t\t);\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector);\n\t\tui.start();\n\t});\n}\n\nexport interface MainOptions {\n\textensionFactories?: ExtensionFactory[];\n}\n\nexport async function main(args: string[], options?: MainOptions) {\n\tresetTimings();\n\tconst offlineMode = args.includes(\"--offline\") || isTruthyEnvFlag(process.env.OMK_OFFLINE);\n\tif (offlineMode) {\n\t\tprocess.env.OMK_OFFLINE = \"1\";\n\t\tprocess.env.OMK_SKIP_VERSION_CHECK = \"1\";\n\t}\n\n\tif (process.platform === \"win32\") {\n\t\tcleanupWindowsSelfUpdateQuarantine(getPackageDir());\n\t}\n\n\tconst packageDoctor = await runPackageDoctorCli(args);\n\tif (packageDoctor.handled) {\n\t\tprocess.exitCode = packageDoctor.exitCode;\n\t\treturn;\n\t}\n\n\tif (await handlePackageCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleConfigCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleCodexBarQuotaCommand(args)) {\n\t\treturn;\n\t}\n\n\t// Subcommands that report their own outcome, tried in order. Each claims a\n\t// distinct command prefix, so the first one that reports `handled` wins.\n\ttype CliOutcome = { readonly handled: boolean; readonly exitCode: number };\n\tconst outcomeCommands: ReadonlyArray<(argv: string[]) => CliOutcome | Promise<CliOutcome>> = [\n\t\trunSessionDoctorCli,\n\t\trunDoctorProviderCli,\n\t\trunResourceDoctorCli,\n\t\trunAdaptOrchDoctorCli,\n\t\trunStatsCli,\n\t\trunSdkSessionCli,\n\t\trunRouterFeedbackCli,\n\t];\n\tfor (const runCommand of outcomeCommands) {\n\t\tconst outcome = await runCommand(args);\n\t\tif (outcome.handled) {\n\t\t\tprocess.exitCode = outcome.exitCode;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tconst parsed = parseArgs(args);\n\tif (parsed.diagnostics.length > 0) {\n\t\tfor (const d of parsed.diagnostics) {\n\t\t\tconst color = d.type === \"error\" ? chalk.red : chalk.yellow;\n\t\t\tconsole.error(color(`${d.type === \"error\" ? \"Error\" : \"Warning\"}: ${d.message}`));\n\t\t}\n\t\tif (parsed.diagnostics.some((d) => d.type === \"error\")) {\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"parseArgs\");\n\tlet appMode = resolveAppMode(parsed, !!process.stdin.isTTY, !!process.stdout.isTTY);\n\t// Bare interactive launch: try to reclaim the controlling TTY when a host\n\t// detached stdio. Launcher may already have re-exec'd under `script`.\n\tif (appMode === \"interactive\" && !hasPromptIntent(parsed)) {\n\t\tif (!process.stdin.isTTY || !process.stdout.isTTY) {\n\t\t\tattachStdioToControllingTty();\n\t\t}\n\t\t// Do not hard-exit here — ProcessTerminal still works on many WSL/Windows\n\t\t// hosts even when Node's isTTY is false after a PTY re-exec race.\n\t}\n\tconst shouldTakeOverStdout = appMode !== \"interactive\";\n\tif (shouldTakeOverStdout) {\n\t\ttakeOverStdout();\n\t}\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.export) {\n\t\tlet result: string;\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tresult = await exportFromFile(parsed.export, outputPath);\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconsole.log(`Exported to: ${result}`);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tvalidateForkFlags(parsed);\n\tvalidateSessionIdFlags(parsed);\n\n\t// Run migrations (pass cwd for project-local migrations)\n\tconst { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());\n\ttime(\"runMigrations\");\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst startupSettingsManager = SettingsManager.create(cwd, agentDir);\n\treportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, \"startup session lookup\"));\n\n\t// Decide the final runtime cwd before creating cwd-bound runtime services.\n\t// --session and --resume may select a session from another project, so project-local\n\t// settings, resources, provider registrations, and models must be resolved only after\n\t// the target session cwd is known. The startup-cwd settings manager is used only for\n\t// sessionDir lookup during session selection.\n\tconst envSessionDir = process.env[ENV_SESSION_DIR];\n\tconst sessionDir =\n\t\t(parsed.sessionDir ? normalizePath(parsed.sessionDir) : undefined) ??\n\t\t(envSessionDir ? expandTildePath(envSessionDir) : undefined) ??\n\t\tstartupSettingsManager.getSessionDir();\n\tlet sessionManager = await createSessionManager(parsed, cwd, sessionDir, startupSettingsManager);\n\tconst missingSessionCwdIssue = getMissingSessionCwdIssue(sessionManager, cwd);\n\tif (missingSessionCwdIssue) {\n\t\tif (appMode === \"interactive\") {\n\t\t\tconst selectedCwd = await promptForMissingSessionCwd(missingSessionCwdIssue, startupSettingsManager);\n\t\t\tif (!selectedCwd) {\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\tsessionManager = SessionManager.open(missingSessionCwdIssue.sessionFile!, sessionDir, selectedCwd);\n\t\t} else {\n\t\t\tconsole.error(chalk.red(new MissingSessionCwdError(missingSessionCwdIssue).message));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\tif (parsed.name !== undefined) {\n\t\tconst name = parsed.name.trim();\n\t\tif (!name) {\n\t\t\tconsole.error(chalk.red(\"Error: --name requires a non-empty value\"));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tsessionManager.appendSessionInfo(name);\n\t}\n\ttime(\"createSessionManager\");\n\n\tconst resolvedExtensionPaths = resolveCliPaths(cwd, parsed.extensions);\n\tconst resolvedSkillPaths = resolveCliPaths(cwd, parsed.skills);\n\tconst resolvedPromptTemplatePaths = resolveCliPaths(cwd, parsed.promptTemplates);\n\tconst resolvedThemePaths = resolveCliPaths(cwd, parsed.themes);\n\tconst authStorage = AuthStorage.create();\n\tconst createRuntime: CreateAgentSessionRuntimeFactory = async ({\n\t\tcwd,\n\t\tagentDir,\n\t\tsessionManager,\n\t\tsessionStartEvent,\n\t}) => {\n\t\tconst services = await createAgentSessionServices({\n\t\t\tcwd,\n\t\t\tagentDir,\n\t\t\tauthStorage,\n\t\t\textensionFlagValues: parsed.unknownFlags,\n\t\t\tresourceLoaderOptions: {\n\t\t\t\tadditionalExtensionPaths: resolvedExtensionPaths,\n\t\t\t\tadditionalSkillPaths: resolvedSkillPaths,\n\t\t\t\tadditionalPromptTemplatePaths: resolvedPromptTemplatePaths,\n\t\t\t\tadditionalThemePaths: resolvedThemePaths,\n\t\t\t\tnoExtensions: parsed.noExtensions,\n\t\t\t\tnoSkills: parsed.noSkills,\n\t\t\t\tnoPromptTemplates: parsed.noPromptTemplates,\n\t\t\t\tnoThemes: parsed.noThemes,\n\t\t\t\tnoContextFiles: parsed.noContextFiles,\n\t\t\t\tsystemPrompt: parsed.systemPrompt,\n\t\t\t\tappendSystemPrompt: parsed.appendSystemPrompt,\n\t\t\t\textensionFactories: options?.extensionFactories,\n\t\t\t},\n\t\t});\n\t\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\t\t// A discovered package extension that fails to load must not kill the process.\n\t\t// Any \"error\" diagnostic exits 1 below, and headless runs (subagents, review and\n\t\t// QA lanes) go through this same path — so one stale third-party package takes\n\t\t// down every spawned agent while the interactive session looks healthy. Only an\n\t\t// extension the user asked for by hand (-e/--extension) stays fatal.\n\t\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [\n\t\t\t...services.diagnostics,\n\t\t\t...collectSettingsDiagnostics(settingsManager, \"runtime creation\"),\n\t\t\t...resourceLoader.getExtensions().errors.map(({ path, error }) => ({\n\t\t\t\ttype: isExplicitExtensionDiagnostic(path, resolvedExtensionPaths)\n\t\t\t\t\t? (\"error\" as const)\n\t\t\t\t\t: (\"warning\" as const),\n\t\t\t\tmessage: `Failed to load extension \"${path}\": ${error}`,\n\t\t\t})),\n\t\t];\n\n\t\tconst modelPatterns = parsed.models ?? settingsManager.getEnabledModels();\n\t\tconst scopedModels =\n\t\t\tmodelPatterns && modelPatterns.length > 0 ? await resolveModelScope(modelPatterns, modelRegistry) : [];\n\t\tconst {\n\t\t\toptions: sessionOptions,\n\t\t\tcliThinkingFromModel,\n\t\t\tdiagnostics: sessionOptionDiagnostics,\n\t\t} = buildSessionOptions(\n\t\t\tparsed,\n\t\t\tscopedModels,\n\t\t\tsessionManager.buildSessionContext().messages.length > 0,\n\t\t\tmodelRegistry,\n\t\t\tsettingsManager,\n\t\t);\n\t\tdiagnostics.push(...sessionOptionDiagnostics);\n\n\t\tif (parsed.apiKey) {\n\t\t\tif (!sessionOptions.model) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tmessage: \"--api-key requires a model to be specified via --model, --provider/--model, or --models\",\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tauthStorage.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);\n\t\t\t}\n\t\t}\n\n\t\tconst created = await createAgentSessionFromServices({\n\t\t\tservices,\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent,\n\t\t\tmodel: sessionOptions.model,\n\t\t\tthinkingLevel: sessionOptions.thinkingLevel,\n\t\t\tscopedModels: sessionOptions.scopedModels,\n\t\t\tmodelPinned: sessionOptions.modelPinned,\n\t\t\ttools: sessionOptions.tools,\n\t\t\texcludeTools: sessionOptions.excludeTools,\n\t\t\tnoTools: sessionOptions.noTools,\n\t\t\tcustomTools: sessionOptions.customTools,\n\t\t});\n\t\tconst cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;\n\t\tif (created.session.model && cliThinkingOverride) {\n\t\t\tcreated.session.setThinkingLevel(created.session.thinkingLevel);\n\t\t}\n\n\t\treturn {\n\t\t\t...created,\n\t\t\tservices,\n\t\t\tdiagnostics,\n\t\t};\n\t};\n\ttime(\"createRuntime\");\n\tconst runtime = await createAgentSessionRuntime(createRuntime, {\n\t\tcwd: sessionManager.getCwd(),\n\t\tagentDir,\n\t\tsessionManager,\n\t});\n\ttime(\"createAgentSessionRuntime\");\n\tconst { services, session, modelFallbackMessage } = runtime;\n\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\tconfigureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());\n\n\tif (parsed.help) {\n\t\tconst extensionFlags = resourceLoader\n\t\t\t.getExtensions()\n\t\t\t.extensions.flatMap((extension) => Array.from(extension.flags.values()));\n\t\tprintHelp(extensionFlags);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(modelRegistry, searchPattern);\n\t\tprocess.exit(0);\n\t}\n\n\t// Read piped stdin content (if any) - skip for RPC mode which uses stdin for JSON-RPC.\n\t// Also skip on bare interactive launches so a stuck/non-tty stdin cannot force print mode.\n\tlet stdinContent: string | undefined;\n\tif (appMode !== \"rpc\" && !(appMode === \"interactive\" && !hasPromptIntent(parsed))) {\n\t\tstdinContent = await readPipedStdin();\n\t\tif (stdinContent !== undefined && appMode === \"interactive\") {\n\t\t\tappMode = \"print\";\n\t\t}\n\t}\n\ttime(\"readPipedStdin\");\n\n\tconst { initialMessage, initialImages } = await prepareInitialMessage(\n\t\tparsed,\n\t\tsettingsManager.getImageAutoResize(),\n\t\tstdinContent,\n\t);\n\ttime(\"prepareInitialMessage\");\n\tinitTheme(settingsManager.getTheme(), appMode === \"interactive\");\n\ttime(\"initTheme\");\n\n\t// Show deprecation warnings in interactive mode\n\tif (appMode === \"interactive\" && deprecationWarnings.length > 0) {\n\t\tawait showDeprecationWarnings(deprecationWarnings);\n\t}\n\n\ttime(\"resolveModelScope\");\n\treportDiagnostics(runtime.diagnostics);\n\tif (runtime.diagnostics.some((diagnostic) => diagnostic.type === \"error\")) {\n\t\tprocess.exit(1);\n\t}\n\ttime(\"createAgentSession\");\n\n\tif (appMode !== \"interactive\" && !session.model) {\n\t\tconsole.error(chalk.red(formatNoModelsAvailableMessage()));\n\t\tprocess.exit(1);\n\t}\n\n\tconst startupBenchmark = isTruthyEnvFlag(process.env.OMK_STARTUP_BENCHMARK);\n\tif (startupBenchmark && appMode !== \"interactive\") {\n\t\tconsole.error(chalk.red(\"Error: OMK_STARTUP_BENCHMARK only supports interactive mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tif (appMode === \"rpc\") {\n\t\tprintTimings();\n\t\tawait runRpcMode(runtime);\n\t} else if (appMode === \"interactive\") {\n\t\tconst interactiveMode = new InteractiveMode(runtime, {\n\t\t\tmigratedProviders,\n\t\t\tmodelFallbackMessage,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\tinitialMessages: parsed.messages,\n\t\t\tverbose: parsed.verbose,\n\t\t});\n\t\tif (startupBenchmark) {\n\t\t\tawait interactiveMode.init();\n\t\t\ttime(\"interactiveMode.init\");\n\t\t\tprintTimings();\n\t\t\tinteractiveMode.stop();\n\t\t\tstopThemeWatcher();\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tprintTimings();\n\t\tawait interactiveMode.run();\n\t} else {\n\t\tprintTimings();\n\t\tconst exitCode = await runPrintMode(runtime, {\n\t\t\tmode: toPrintOutputMode(appMode),\n\t\t\tmessages: parsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t});\n\t\tstopThemeWatcher();\n\t\trestoreStdout();\n\t\tif (exitCode !== 0) {\n\t\t\tprocess.exitCode = exitCode;\n\t\t}\n\t\treturn;\n\t}\n}\n"]}
|