@claudexor/cli 3.24.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account-management.d.ts +17 -0
- package/dist/account-management.d.ts.map +1 -0
- package/dist/account-management.js +35 -0
- package/dist/account-management.js.map +1 -0
- package/dist/account-reset-command.d.ts +3 -0
- package/dist/account-reset-command.d.ts.map +1 -0
- package/dist/account-reset-command.js +32 -0
- package/dist/account-reset-command.js.map +1 -0
- package/dist/account-reset-services.d.ts +341 -0
- package/dist/account-reset-services.d.ts.map +1 -0
- package/dist/account-reset-services.js +183 -0
- package/dist/account-reset-services.js.map +1 -0
- package/dist/account-resource-query.d.ts +18 -0
- package/dist/account-resource-query.d.ts.map +1 -0
- package/dist/account-resource-query.js +62 -0
- package/dist/account-resource-query.js.map +1 -0
- package/dist/accounts-services.d.ts +1 -0
- package/dist/accounts-services.d.ts.map +1 -1
- package/dist/accounts-services.js +4 -2
- package/dist/accounts-services.js.map +1 -1
- package/dist/claude-account-resources.d.ts +5 -0
- package/dist/claude-account-resources.d.ts.map +1 -0
- package/dist/claude-account-resources.js +134 -0
- package/dist/claude-account-resources.js.map +1 -0
- package/dist/claude-oauth-codec.d.ts +4 -0
- package/dist/claude-oauth-codec.d.ts.map +1 -0
- package/dist/claude-oauth-codec.js +71 -0
- package/dist/claude-oauth-codec.js.map +1 -0
- package/dist/claude-oauth-credential.d.ts +6 -0
- package/dist/claude-oauth-credential.d.ts.map +1 -1
- package/dist/claude-oauth-credential.js +18 -0
- package/dist/claude-oauth-credential.js.map +1 -1
- package/dist/claude-oauth-fetch.d.ts +1 -1
- package/dist/claude-oauth-fetch.d.ts.map +1 -1
- package/dist/claude-oauth-fetch.js +3 -2
- package/dist/claude-oauth-fetch.js.map +1 -1
- package/dist/claude-oauth-usage.d.ts +5 -4
- package/dist/claude-oauth-usage.d.ts.map +1 -1
- package/dist/claude-oauth-usage.js +47 -75
- package/dist/claude-oauth-usage.js.map +1 -1
- package/dist/claude-resource-source.d.ts +18 -0
- package/dist/claude-resource-source.d.ts.map +1 -0
- package/dist/claude-resource-source.js +108 -0
- package/dist/claude-resource-source.js.map +1 -0
- package/dist/claude-resource-transport.d.ts +12 -0
- package/dist/claude-resource-transport.d.ts.map +1 -0
- package/dist/claude-resource-transport.js +47 -0
- package/dist/claude-resource-transport.js.map +1 -0
- package/dist/claude-statusline.d.ts +2 -4
- package/dist/claude-statusline.d.ts.map +1 -1
- package/dist/claude-statusline.js +6 -5
- package/dist/claude-statusline.js.map +1 -1
- package/dist/claudexord-entry.d.ts +7 -2
- package/dist/claudexord-entry.d.ts.map +1 -1
- package/dist/claudexord-entry.js +10 -3
- package/dist/claudexord-entry.js.map +1 -1
- package/dist/claudexord.d.ts +0 -1
- package/dist/claudexord.d.ts.map +1 -1
- package/dist/claudexord.js +308 -239
- package/dist/claudexord.js.map +1 -1
- package/dist/cli-error.js +3 -3
- package/dist/cli-error.js.map +1 -1
- package/dist/cli.js +7 -49
- package/dist/cli.js.map +1 -1
- package/dist/codex-quota-source.d.ts +7 -2
- package/dist/codex-quota-source.d.ts.map +1 -1
- package/dist/codex-quota-source.js +74 -14
- package/dist/codex-quota-source.js.map +1 -1
- package/dist/command-flags.d.ts.map +1 -1
- package/dist/command-flags.js +6 -0
- package/dist/command-flags.js.map +1 -1
- package/dist/command-registry.js +1 -1
- package/dist/command-registry.js.map +1 -1
- package/dist/continue-from-run.d.ts +7 -8
- package/dist/continue-from-run.d.ts.map +1 -1
- package/dist/continue-from-run.js +3 -4
- package/dist/continue-from-run.js.map +1 -1
- package/dist/control-api-address.d.ts +6 -0
- package/dist/control-api-address.d.ts.map +1 -0
- package/dist/control-api-address.js +53 -0
- package/dist/control-api-address.js.map +1 -0
- package/dist/control-services.d.ts +400 -230
- package/dist/control-services.d.ts.map +1 -1
- package/dist/control-services.js +11 -32
- package/dist/control-services.js.map +1 -1
- package/dist/daemon-admission-runtime.d.ts +0 -60
- package/dist/daemon-admission-runtime.d.ts.map +1 -1
- package/dist/daemon-admission-runtime.js +3 -168
- package/dist/daemon-admission-runtime.js.map +1 -1
- package/dist/daemon-agent-runner.d.ts +6 -3
- package/dist/daemon-agent-runner.d.ts.map +1 -1
- package/dist/daemon-agent-runner.js +2 -1
- package/dist/daemon-agent-runner.js.map +1 -1
- package/dist/daemon-lifecycle.d.ts +10 -3
- package/dist/daemon-lifecycle.d.ts.map +1 -1
- package/dist/daemon-lifecycle.js +16 -7
- package/dist/daemon-lifecycle.js.map +1 -1
- package/dist/daemon-owner.d.ts +7 -0
- package/dist/daemon-owner.d.ts.map +1 -0
- package/dist/daemon-owner.js +39 -0
- package/dist/daemon-owner.js.map +1 -0
- package/dist/daemon-run.d.ts +8 -1
- package/dist/daemon-run.d.ts.map +1 -1
- package/dist/daemon-run.js +31 -3
- package/dist/daemon-run.js.map +1 -1
- package/dist/daemon-runtime-shutdown.d.ts +1 -1
- package/dist/daemon-runtime-shutdown.d.ts.map +1 -1
- package/dist/daemon-runtime-shutdown.js +2 -2
- package/dist/daemon-runtime-shutdown.js.map +1 -1
- package/dist/daemon-startup.d.ts +5 -42
- package/dist/daemon-startup.d.ts.map +1 -1
- package/dist/daemon-startup.js +7 -85
- package/dist/daemon-startup.js.map +1 -1
- package/dist/daemon-status-report.d.ts +4 -0
- package/dist/daemon-status-report.d.ts.map +1 -0
- package/dist/daemon-status-report.js +39 -0
- package/dist/daemon-status-report.js.map +1 -0
- package/dist/engine-skew.d.ts +1 -0
- package/dist/engine-skew.d.ts.map +1 -1
- package/dist/engine-skew.js +6 -2
- package/dist/engine-skew.js.map +1 -1
- package/dist/harness-maintenance-service.d.ts +9 -222
- package/dist/harness-maintenance-service.d.ts.map +1 -1
- package/dist/harness-maintenance-service.js +7 -37
- package/dist/harness-maintenance-service.js.map +1 -1
- package/dist/live.d.ts +2 -5
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +2 -44
- package/dist/live.js.map +1 -1
- package/dist/mcp-catalog-query.d.ts +2 -1
- package/dist/mcp-catalog-query.d.ts.map +1 -1
- package/dist/mcp-catalog-query.js +6 -5
- package/dist/mcp-catalog-query.js.map +1 -1
- package/dist/mcp-daemon-unavailable.d.ts.map +1 -1
- package/dist/mcp-daemon-unavailable.js +4 -3
- package/dist/mcp-daemon-unavailable.js.map +1 -1
- package/dist/mcp-journal-recovery.d.ts.map +1 -1
- package/dist/mcp-journal-recovery.js +4 -4
- package/dist/mcp-journal-recovery.js.map +1 -1
- package/dist/mcp-runner.d.ts +1 -1
- package/dist/mcp-runner.d.ts.map +1 -1
- package/dist/mcp-runner.js +11 -4
- package/dist/mcp-runner.js.map +1 -1
- package/dist/mcp-thread-query.d.ts +1 -1
- package/dist/mcp-thread-query.d.ts.map +1 -1
- package/dist/mcp-thread-query.js +2 -2
- package/dist/mcp-thread-query.js.map +1 -1
- package/dist/model-operation-control.d.ts +375 -0
- package/dist/model-operation-control.d.ts.map +1 -0
- package/dist/model-operation-control.js +16 -0
- package/dist/model-operation-control.js.map +1 -0
- package/dist/model-services.d.ts +302 -91
- package/dist/model-services.d.ts.map +1 -1
- package/dist/model-services.js +5 -7
- package/dist/model-services.js.map +1 -1
- package/dist/ops-command-specs.d.ts.map +1 -1
- package/dist/ops-command-specs.js +11 -2
- package/dist/ops-command-specs.js.map +1 -1
- package/dist/ops-commands.d.ts +1 -8
- package/dist/ops-commands.d.ts.map +1 -1
- package/dist/ops-commands.js +13 -16
- package/dist/ops-commands.js.map +1 -1
- package/dist/orphan-reaper.d.ts +18 -0
- package/dist/orphan-reaper.d.ts.map +1 -1
- package/dist/orphan-reaper.js +50 -0
- package/dist/orphan-reaper.js.map +1 -1
- package/dist/plugin-cli.d.ts +3 -0
- package/dist/plugin-cli.d.ts.map +1 -0
- package/dist/plugin-cli.js +57 -0
- package/dist/plugin-cli.js.map +1 -0
- package/dist/plugin-mcp.d.ts +3 -0
- package/dist/plugin-mcp.d.ts.map +1 -0
- package/dist/plugin-mcp.js +116 -0
- package/dist/plugin-mcp.js.map +1 -0
- package/dist/plugin-runtime.d.ts +24 -0
- package/dist/plugin-runtime.d.ts.map +1 -0
- package/dist/plugin-runtime.js +113 -0
- package/dist/plugin-runtime.js.map +1 -0
- package/dist/plugins.d.ts +2 -0
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +92 -184
- package/dist/plugins.js.map +1 -1
- package/dist/project-command.d.ts +4 -1
- package/dist/project-command.d.ts.map +1 -1
- package/dist/project-command.js +14 -5
- package/dist/project-command.js.map +1 -1
- package/dist/quota-command.d.ts.map +1 -1
- package/dist/quota-command.js +38 -2
- package/dist/quota-command.js.map +1 -1
- package/dist/quota-refreshers.d.ts +3 -1
- package/dist/quota-refreshers.d.ts.map +1 -1
- package/dist/quota-refreshers.js +6 -2
- package/dist/quota-refreshers.js.map +1 -1
- package/dist/quota-services.d.ts +236 -2
- package/dist/quota-services.d.ts.map +1 -1
- package/dist/quota-services.js +84 -4
- package/dist/quota-services.js.map +1 -1
- package/dist/recovery-control-services.d.ts +99 -0
- package/dist/recovery-control-services.d.ts.map +1 -0
- package/dist/recovery-control-services.js +32 -0
- package/dist/recovery-control-services.js.map +1 -0
- package/dist/remote-filesystem.d.ts +3 -3
- package/dist/remote-filesystem.d.ts.map +1 -1
- package/dist/remote-filesystem.js.map +1 -1
- package/dist/request-preflight.d.ts +2 -2
- package/dist/request-preflight.d.ts.map +1 -1
- package/dist/request-preflight.js.map +1 -1
- package/dist/retention-service.d.ts +3 -3
- package/dist/retention-service.d.ts.map +1 -1
- package/dist/settings-command.d.ts.map +1 -1
- package/dist/settings-command.js +7 -2
- package/dist/settings-command.js.map +1 -1
- package/dist/settings-service.d.ts +66 -6
- package/dist/settings-service.d.ts.map +1 -1
- package/dist/settings-service.js +1 -1
- package/dist/settings-service.js.map +1 -1
- package/dist/setup-client-pty.js +1 -1
- package/dist/setup-client-pty.js.map +1 -1
- package/dist/setup-credential-window.d.ts +4 -4
- package/dist/setup-credential-window.d.ts.map +1 -1
- package/dist/setup-credential-window.js +1 -1
- package/dist/setup-credential-window.js.map +1 -1
- package/dist/setup-device-code-disclosure.js +1 -1
- package/dist/setup-device-code-disclosure.js.map +1 -1
- package/dist/setup-job-persistence.d.ts +30 -0
- package/dist/setup-job-persistence.d.ts.map +1 -0
- package/dist/setup-job-persistence.js +3 -0
- package/dist/setup-job-persistence.js.map +1 -0
- package/dist/{setup-job-store.d.ts → setup-job-projection.d.ts} +9 -17
- package/dist/setup-job-projection.d.ts.map +1 -0
- package/dist/{setup-job-store.js → setup-job-projection.js} +29 -68
- package/dist/setup-job-projection.js.map +1 -0
- package/dist/setup-job-support.js +1 -1
- package/dist/setup-job-support.js.map +1 -1
- package/dist/setup-jobs.d.ts +3 -4
- package/dist/setup-jobs.d.ts.map +1 -1
- package/dist/setup-jobs.js +3 -3
- package/dist/setup-jobs.js.map +1 -1
- package/dist/setup-lifecycle-binding.js +1 -1
- package/dist/setup-lifecycle-binding.js.map +1 -1
- package/dist/setup-login-completion.js +1 -1
- package/dist/setup-login-completion.js.map +1 -1
- package/dist/sql-daemon-queries.d.ts +24 -0
- package/dist/sql-daemon-queries.d.ts.map +1 -0
- package/dist/sql-daemon-queries.js +41 -0
- package/dist/sql-daemon-queries.js.map +1 -0
- package/dist/sql-daemon-runtime.d.ts +1415 -0
- package/dist/sql-daemon-runtime.d.ts.map +1 -0
- package/dist/sql-daemon-runtime.js +94 -0
- package/dist/sql-daemon-runtime.js.map +1 -0
- package/dist/sql-daemon-storage.d.ts +40 -0
- package/dist/sql-daemon-storage.d.ts.map +1 -0
- package/dist/sql-daemon-storage.js +203 -0
- package/dist/sql-daemon-storage.js.map +1 -0
- package/dist/sql-import-publication.d.ts +19 -0
- package/dist/sql-import-publication.d.ts.map +1 -0
- package/dist/sql-import-publication.js +54 -0
- package/dist/sql-import-publication.js.map +1 -0
- package/dist/sql-setup-lifecycle.d.ts +15 -0
- package/dist/sql-setup-lifecycle.d.ts.map +1 -0
- package/dist/sql-setup-lifecycle.js +28 -0
- package/dist/sql-setup-lifecycle.js.map +1 -0
- package/dist/sql-setup-store.d.ts +7 -0
- package/dist/sql-setup-store.d.ts.map +1 -0
- package/dist/sql-setup-store.js +102 -0
- package/dist/sql-setup-store.js.map +1 -0
- package/dist/sql-startup-storage.d.ts +15 -0
- package/dist/sql-startup-storage.d.ts.map +1 -0
- package/dist/sql-startup-storage.js +75 -0
- package/dist/sql-startup-storage.js.map +1 -0
- package/dist/thread-delivery.d.ts +2 -2
- package/dist/thread-purge.d.ts +8 -2
- package/dist/thread-purge.d.ts.map +1 -1
- package/dist/thread-purge.js +37 -8
- package/dist/thread-purge.js.map +1 -1
- package/dist/thread-turn-services.d.ts +2 -2
- package/dist/thread-turn-services.d.ts.map +1 -1
- package/dist/thread-turn-services.js.map +1 -1
- package/package.json +25 -26
- package/dist/setup-job-store.d.ts.map +0 -1
- package/dist/setup-job-store.js.map +0 -1
package/dist/claudexord.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
+
import { DaemonLocalClient, DaemonServer, RunEventBus, quotaPacerFileStore, daemonDir, defaultSocketPath, acquireRootAuthority, ensureToken, ensureDaemonRuntimeRoot, logPath, socketAlive, loadEngineRuntime, recordAdmissionMemory, processMemoryFields, recoveryOnlyRefusal, } from "@claudexor/daemon";
|
|
4
5
|
import { DaemonControlApiServer } from "@claudexor/control-api";
|
|
5
|
-
import { createDaemonQuotaPoller,
|
|
6
|
-
import { armDaemonLifecycle,
|
|
7
|
-
import { bindRecoveryTransport,
|
|
6
|
+
import { createDaemonQuotaPoller, openStartupDiagnostics } from "./daemon-admission-runtime.js";
|
|
7
|
+
import { armDaemonLifecycle, runStartupCrashGc } from "./daemon-lifecycle.js";
|
|
8
|
+
import { bindRecoveryTransport, DaemonStartupAdmission, proveRecoveryTransport, quarantineGhostProjectsAtStartup, } from "./daemon-startup.js";
|
|
8
9
|
import { engineBuildIdentity, noProjectRepoRoot, redactSecrets } from "@claudexor/util";
|
|
9
|
-
import {
|
|
10
|
-
import { runtimeConcurrencyCaps } from "@claudexor/schema";
|
|
10
|
+
import { loadRuntimeConcurrencyCaps, sweepRetiredConfigKeysAtStartup } from "@claudexor/config";
|
|
11
11
|
import { scheduleStartupRetention } from "./retention-service.js";
|
|
12
12
|
import { controlServices } from "./control-services.js";
|
|
13
|
+
import { recoveryControlServices } from "./recovery-control-services.js";
|
|
13
14
|
import { AuthReadinessService } from "@claudexor/gateway";
|
|
14
15
|
import { bindCredentialMutationWindow } from "@claudexor/core";
|
|
15
16
|
import { buildGateway } from "./registry.js";
|
|
16
17
|
import { createSetupJobManager } from "./setup-jobs.js";
|
|
17
18
|
import { bustLoginCredentialState } from "./credential-status-invalidation.js";
|
|
18
|
-
import { SetupJobStore } from "./setup-job-store.js";
|
|
19
19
|
import { SetupLifecycleBinding } from "./setup-lifecycle-binding.js";
|
|
20
|
+
import { SqlSetupLifecycleSlot } from "./sql-setup-lifecycle.js";
|
|
20
21
|
import { DaemonRuntimeShutdown } from "./daemon-runtime-shutdown.js";
|
|
21
22
|
import { quotaRefreshers } from "./quota-refreshers.js";
|
|
22
23
|
import { dispatchClaudexordEntry, runIfDirectEntry, runProbeIfRequested, } from "./claudexord-entry.js";
|
|
@@ -24,289 +25,357 @@ import { createDelegationDaemonBinding } from "./delegation-daemon-binding.js";
|
|
|
24
25
|
import { quotaSubjectUniverseFromConfig } from "./quota-subject-universe.js";
|
|
25
26
|
import { runStartupAccountsMigration } from "./accounts-unified-migration.js";
|
|
26
27
|
import { runStopIfRequested } from "./runtime-replacement-stop.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
28
|
+
import { createSqlDaemonExecutors } from "./sql-daemon-runtime.js";
|
|
29
|
+
import { SqlDaemonStorage } from "./sql-daemon-storage.js";
|
|
30
|
+
import { deferredCommands, sqlActivityRecords, sqlStartupRoots } from "./sql-daemon-queries.js";
|
|
31
|
+
import { threadPurgeEffect } from "./thread-purge.js";
|
|
32
|
+
import { accountResetServices } from "./account-reset-services.js";
|
|
31
33
|
export async function main() {
|
|
32
|
-
// Probe and identity-proven stop must run before any durable startup.
|
|
33
34
|
if (runProbeIfRequested(process.argv.slice(2)))
|
|
34
35
|
return;
|
|
35
36
|
if (await runStopIfRequested(process.argv.slice(2)))
|
|
36
37
|
return;
|
|
37
|
-
|
|
38
|
+
// Refuse unsupported Node/SQLite before touching the root or lease.
|
|
39
|
+
await loadEngineRuntime();
|
|
40
|
+
const identity = engineBuildIdentity();
|
|
38
41
|
ensureDaemonRuntimeRoot();
|
|
39
42
|
const socketPath = defaultSocketPath();
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// can still write, and releasing would let a successor acquire ownership
|
|
48
|
-
// beside them. On failure the lease dies with the process instead.
|
|
49
|
-
let releaseWriterLease = true;
|
|
43
|
+
const authority = acquireRootAuthority({ socketPath, version: identity.version });
|
|
44
|
+
const diagnostics = openStartupDiagnostics(identity);
|
|
45
|
+
const log = (message) => diagnostics.log("sql_runtime", message);
|
|
46
|
+
const admission = new DaemonStartupAdmission();
|
|
47
|
+
const abort = new AbortController();
|
|
48
|
+
let cleanShutdown = true;
|
|
49
|
+
let shutdown = null;
|
|
50
50
|
let lifecycle = null;
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
let control = null;
|
|
52
|
+
let opening = null;
|
|
53
|
+
let storage;
|
|
54
|
+
let setup = null;
|
|
55
|
+
let executors = null;
|
|
56
|
+
let server;
|
|
57
|
+
let listening = false;
|
|
58
|
+
let admissionFlight = null;
|
|
59
|
+
let dutiesFor = null;
|
|
60
|
+
const graph = () => storage.graph();
|
|
61
|
+
const quotaPoller = createDaemonQuotaPoller(() => {
|
|
62
|
+
if (admission.snapshot() !== "normal")
|
|
63
|
+
return;
|
|
64
|
+
void Promise.resolve()
|
|
65
|
+
.then(() => graph().quota.pollStale())
|
|
66
|
+
.catch((error) => log(`quota poll: ${String(error)}`));
|
|
67
|
+
});
|
|
68
|
+
let readiness;
|
|
69
|
+
function setupFor(value) {
|
|
70
|
+
return new SetupLifecycleBinding(new SqlSetupLifecycleSlot(daemonDir(), value), (store) => createSetupJobManager({
|
|
71
|
+
rootDir: daemonDir(),
|
|
72
|
+
store,
|
|
73
|
+
onCredentialStateMayHaveChanged: (harness) => bustLoginCredentialState(() => value.quota, readiness, harness),
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
55
76
|
try {
|
|
77
|
+
readiness = new AuthReadinessService(buildGateway({ includeFakes: false }), {
|
|
78
|
+
cwd: noProjectRepoRoot(),
|
|
79
|
+
});
|
|
56
80
|
const token = ensureToken();
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
if (await socketAlive(socketPath)) {
|
|
81
|
+
const caps = loadRuntimeConcurrencyCaps(noProjectRepoRoot());
|
|
82
|
+
if (await socketAlive(socketPath))
|
|
60
83
|
throw new Error(`a claudexor daemon is already listening on ${socketPath}; stop it first`);
|
|
61
|
-
}
|
|
62
84
|
const bus = new RunEventBus();
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
85
|
+
const delegation = createDelegationDaemonBinding();
|
|
86
|
+
const selfClient = new DaemonLocalClient(() => server);
|
|
87
|
+
const services = {};
|
|
88
|
+
const makeServer = () => {
|
|
89
|
+
const next = new DaemonServer({
|
|
90
|
+
socketPath,
|
|
91
|
+
token,
|
|
92
|
+
commands: deferredCommands(() => graph().commands),
|
|
93
|
+
runtimeConcurrencyCaps: caps,
|
|
94
|
+
servingMode: admission.snapshot,
|
|
95
|
+
storeFacts: () => storage.facts(),
|
|
96
|
+
delegationAuthority: delegation.authority,
|
|
97
|
+
runtimeIdentity: { version: identity.version, buildSha: identity.sha },
|
|
98
|
+
runtimeLeaseOwner: authority.lease.owner,
|
|
99
|
+
onCommandTerminal: (record) => executors?.onCommandTerminal(record),
|
|
100
|
+
onRunTerminal: (runId, threadId) => executors?.onRunTerminal(runId, threadId),
|
|
101
|
+
onTurnEnqueueFailed: (turnId, problem) => executors?.onTurnEnqueueFailed(turnId, problem),
|
|
102
|
+
onShutdownRequested: () => shutdown.beginShutdown("socket-rpc stop"),
|
|
103
|
+
onRuntimeReplacementRequested: () => shutdown.beginRuntimeReplacement(),
|
|
104
|
+
runner: (params, ctx) => {
|
|
105
|
+
if (!executors)
|
|
106
|
+
throw recoveryOnlyRefusal("runner");
|
|
107
|
+
return executors.runner(params, ctx);
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
delegation.bind(next);
|
|
111
|
+
return next;
|
|
112
|
+
};
|
|
113
|
+
server = makeServer();
|
|
114
|
+
const partition = (name) => storage.partition(name);
|
|
115
|
+
const recovery = recoveryControlServices({
|
|
116
|
+
partition,
|
|
117
|
+
setup: () => setup,
|
|
118
|
+
onValidated: (name, result) => {
|
|
119
|
+
if (result.status === "recovery_required" && name === "global")
|
|
120
|
+
admission.enterRecoveryOnly();
|
|
121
|
+
},
|
|
122
|
+
afterQuarantine: async () => {
|
|
123
|
+
try {
|
|
124
|
+
await completeAdmission();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
admission.enterRecoveryOnly();
|
|
128
|
+
log(`recovery completed; reopening failed: ${String(error)}`);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
104
131
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
const attach = (value) => {
|
|
133
|
+
if (!listening)
|
|
134
|
+
server = makeServer();
|
|
135
|
+
setup = setupFor(value);
|
|
136
|
+
executors = createSqlDaemonExecutors(value, {
|
|
137
|
+
client: selfClient,
|
|
138
|
+
authority: delegation.authority,
|
|
139
|
+
admission: (id) => server.admission(id),
|
|
140
|
+
authReadiness: () => readiness,
|
|
141
|
+
bus,
|
|
142
|
+
runtimeConcurrencyCaps: caps,
|
|
143
|
+
warn: log,
|
|
144
|
+
});
|
|
145
|
+
const threads = Object.assign(value.threads, { journal: partition });
|
|
146
|
+
const product = controlServices(value.interactions, value.liveInputs, () => value.projects, threads, setup, partition("global"), readiness, () => value.resources, () => value.quota, () => sqlActivityRecords(value.store), caps, value.purgeFiles);
|
|
147
|
+
const retention = executors.models.withRetention(product.runRetention);
|
|
148
|
+
product.runRetention = async (request) => {
|
|
149
|
+
const result = await retention(request);
|
|
150
|
+
if (!request.dry_run)
|
|
151
|
+
value.resources.pruneUploadBindings();
|
|
152
|
+
return result;
|
|
153
|
+
};
|
|
154
|
+
executors.harnessMaintenance.bind(product);
|
|
155
|
+
Object.assign(services, product, executors.models.routes, accountResetServices({ current: () => value.commands.current() }, { current: () => value.quota }), recovery);
|
|
156
|
+
};
|
|
157
|
+
storage = new SqlDaemonStorage({
|
|
158
|
+
rootDir: daemonDir(),
|
|
159
|
+
advanceFloor: () => authority.advanceFloor(),
|
|
160
|
+
graph: {
|
|
161
|
+
purgeFiles: threadPurgeEffect(noProjectRepoRoot()),
|
|
162
|
+
log,
|
|
163
|
+
refreshers: quotaRefreshers((record) => log(`quota.observation ${JSON.stringify(record)}`)),
|
|
164
|
+
subjects: quotaSubjectUniverseFromConfig,
|
|
165
|
+
pacerStore: quotaPacerFileStore(daemonDir()),
|
|
166
|
+
},
|
|
167
|
+
log,
|
|
168
|
+
onOpen: attach,
|
|
169
|
+
onCorrupt: (error) => {
|
|
170
|
+
admission.enterRecoveryOnly();
|
|
171
|
+
quotaPoller.stop();
|
|
172
|
+
log(`engine store recovery required: ${error.message}`);
|
|
173
|
+
void ensureRecoveryControl().catch((problem) => log(`recovery transport unavailable: ${String(problem)}`));
|
|
174
|
+
},
|
|
175
|
+
beforeClose: async () => {
|
|
176
|
+
admission.enterRecoveryOnly();
|
|
177
|
+
quotaPoller.stop();
|
|
178
|
+
setup?.beginDrain();
|
|
179
|
+
executors?.models.close();
|
|
180
|
+
await Promise.all([server.stopForStoreRecovery(), setup?.shutdown()]);
|
|
181
|
+
listening = false;
|
|
182
|
+
setup = null;
|
|
183
|
+
executors = null;
|
|
184
|
+
dutiesFor = null;
|
|
185
|
+
},
|
|
116
186
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
interactions,
|
|
123
|
-
liveInputs,
|
|
124
|
-
resources,
|
|
125
|
-
bus,
|
|
126
|
-
runtimeConcurrencyCaps: startupConcurrencyCaps,
|
|
187
|
+
Object.assign(services, recovery);
|
|
188
|
+
bindCredentialMutationWindow((harness) => {
|
|
189
|
+
if (!setup)
|
|
190
|
+
throw recoveryOnlyRefusal("setup");
|
|
191
|
+
return setup.current().credentialMutationOpen(harness);
|
|
127
192
|
});
|
|
128
|
-
|
|
129
|
-
|
|
193
|
+
let address = null;
|
|
194
|
+
let controlStarting = null;
|
|
195
|
+
const makeControl = () => new DaemonControlApiServer({
|
|
130
196
|
token,
|
|
131
|
-
|
|
132
|
-
|
|
197
|
+
daemon: selfClient,
|
|
198
|
+
port: Number(process.env.CLAUDEXOR_CONTROL_PORT ?? 0),
|
|
133
199
|
servingMode: admission.snapshot,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
interactions.dropForRun(runId);
|
|
138
|
-
liveInputs.dropForRun(runId);
|
|
139
|
-
// Run-terminal is the one W12 path with no thread-store mutation to
|
|
140
|
-
// ride — the terminal changes the thread's presented state, so ping.
|
|
141
|
-
if (threadId)
|
|
142
|
-
threads.pingThreadHead(threadId);
|
|
143
|
-
},
|
|
144
|
-
onTurnEnqueueFailed: (turnId, problem) => threads.setTurnEnqueueError(turnId, problem),
|
|
145
|
-
onShutdownRequested: () => shutdownRuntime?.beginShutdown("socket-rpc stop") ??
|
|
146
|
-
Promise.reject(new Error("daemon shutdown coordinator is not initialized")),
|
|
147
|
-
onRuntimeReplacementRequested: () => {
|
|
148
|
-
if (!shutdownRuntime) {
|
|
149
|
-
throw Object.assign(new Error("daemon shutdown coordinator is not initialized"), {
|
|
150
|
-
code: "runtime_activity_unknown",
|
|
151
|
-
status: 503,
|
|
152
|
-
retryable: true,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
return shutdownRuntime.beginRuntimeReplacement();
|
|
156
|
-
},
|
|
157
|
-
runtimeIdentity: { version: servingIdentity.version, buildSha: servingIdentity.sha },
|
|
158
|
-
runtimeLeaseOwner: rootAuthority.lease.owner,
|
|
159
|
-
runner: (params, ctx) => isModelOperation(params)
|
|
160
|
-
? models.operations.execute(params, ctx)
|
|
161
|
-
: maintenance.owns(params)
|
|
162
|
-
? maintenance.execute(params, ctx)
|
|
163
|
-
: agentRunner(params, ctx),
|
|
164
|
-
});
|
|
165
|
-
bindDelegationDaemon(server);
|
|
166
|
-
const authReadiness = new AuthReadinessService(buildGateway({ includeFakes: false }), {
|
|
167
|
-
cwd: noProjectRepoRoot(),
|
|
200
|
+
bus,
|
|
201
|
+
services,
|
|
202
|
+
terminalFilesPending: (id) => graph().terminalFiles.pending(id),
|
|
168
203
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
204
|
+
async function ensureRecoveryControl() {
|
|
205
|
+
if (shutdown?.requested())
|
|
206
|
+
return;
|
|
207
|
+
if (controlStarting)
|
|
208
|
+
return controlStarting;
|
|
209
|
+
if (control)
|
|
210
|
+
return;
|
|
211
|
+
control = makeControl();
|
|
212
|
+
log("recovery-required SQL state overrides CLAUDEXOR_NO_CONTROL_API=1 for recovery access");
|
|
213
|
+
controlStarting = bindRecoveryTransport({
|
|
214
|
+
server,
|
|
215
|
+
control,
|
|
216
|
+
requested: () => shutdown.requested(),
|
|
217
|
+
daemonDir: daemonDir(),
|
|
218
|
+
logPath: logPath(),
|
|
219
|
+
socketPath,
|
|
220
|
+
}).then((value) => {
|
|
221
|
+
address = value;
|
|
222
|
+
});
|
|
223
|
+
await controlStarting;
|
|
224
|
+
}
|
|
225
|
+
const importing = !existsSync(join(daemonDir(), "engine.sqlite")) && existsSync(join(daemonDir(), "journal"));
|
|
226
|
+
if (process.env.CLAUDEXOR_NO_CONTROL_API !== "1" || importing)
|
|
227
|
+
control = makeControl();
|
|
228
|
+
shutdown = new DaemonRuntimeShutdown({
|
|
179
229
|
daemon: {
|
|
180
230
|
stop: async () => {
|
|
181
|
-
|
|
182
|
-
models.close();
|
|
183
|
-
await
|
|
231
|
+
abort.abort();
|
|
232
|
+
executors?.models.close();
|
|
233
|
+
await server.stop();
|
|
234
|
+
await opening?.catch(() => { });
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
setup: {
|
|
238
|
+
hasActiveWork: () => {
|
|
239
|
+
if (!setup)
|
|
240
|
+
throw recoveryOnlyRefusal("setup activity");
|
|
241
|
+
return setup.hasActiveWork();
|
|
242
|
+
},
|
|
243
|
+
beginDrain: () => setup?.beginDrain(),
|
|
244
|
+
shutdown: async () => {
|
|
245
|
+
await setup?.shutdown();
|
|
184
246
|
},
|
|
185
247
|
},
|
|
186
|
-
setup: setupBinding,
|
|
187
248
|
control: () => control,
|
|
188
249
|
journal: {
|
|
189
|
-
close: () => {
|
|
190
|
-
quotaPoller
|
|
191
|
-
|
|
192
|
-
journalManager.close();
|
|
250
|
+
close: async () => {
|
|
251
|
+
quotaPoller.stop();
|
|
252
|
+
await storage?.close();
|
|
193
253
|
},
|
|
194
254
|
},
|
|
195
|
-
log
|
|
255
|
+
log,
|
|
196
256
|
});
|
|
197
|
-
// Services and retention read all partition activity directly, without self-RPC.
|
|
198
|
-
const services = controlServices(interactions, liveInputs, () => projectStoreSlot.current(), threads, setupBinding, journalManager, authReadiness, resources, () => quotaStoreSlot.current(), () => commandActivityRecords(threads.all().flatMap((store) => store.records())), startupConcurrencyCaps);
|
|
199
|
-
const runRetention = services.runRetention;
|
|
200
|
-
services.runRetention = models.withRetention(runRetention);
|
|
201
|
-
Object.assign(services, models.routes);
|
|
202
|
-
maintenance.bind(services);
|
|
203
|
-
control = !controlApiEnabledForStartup({
|
|
204
|
-
disabledByEnv: process.env.CLAUDEXOR_NO_CONTROL_API === "1",
|
|
205
|
-
blockedPartitions: startupBlockedPartitions,
|
|
206
|
-
log: (message) => logLine(logPath(), message),
|
|
207
|
-
})
|
|
208
|
-
? null
|
|
209
|
-
: new DaemonControlApiServer({
|
|
210
|
-
token,
|
|
211
|
-
daemon: new DaemonClient(socketPath, token),
|
|
212
|
-
port: Number(process.env.CLAUDEXOR_CONTROL_PORT ?? 0),
|
|
213
|
-
servingMode: admission.snapshot,
|
|
214
|
-
bus,
|
|
215
|
-
services,
|
|
216
|
-
});
|
|
217
257
|
lifecycle = armDaemonLifecycle({
|
|
218
258
|
daemonDir: daemonDir(),
|
|
219
259
|
logPath: logPath(),
|
|
220
|
-
...(
|
|
221
|
-
beginShutdown: (reason) =>
|
|
260
|
+
...(diagnostics.diagnostics ? { diagnostics: diagnostics.diagnostics } : {}),
|
|
261
|
+
beginShutdown: (reason) => shutdown.beginShutdown(reason),
|
|
222
262
|
});
|
|
223
|
-
|
|
224
|
-
admission,
|
|
225
|
-
grant: rootAuthority,
|
|
226
|
-
global: journalManager,
|
|
227
|
-
partitions: threads,
|
|
228
|
-
diagnostics: startupDiagnostics,
|
|
229
|
-
knownProjectRoots: () => {
|
|
230
|
-
const commands = commandStoreSlot.prepared();
|
|
231
|
-
return [...commandScopeRoots(commands.records()), ...commands.prunedScopeRoots()];
|
|
232
|
-
},
|
|
233
|
-
// Delegated runs (thread turns live in project partitions) keep their
|
|
234
|
-
// runtime scratch under the caller-owned execution root.
|
|
235
|
-
knownExecutionRoots: () => [
|
|
236
|
-
...commandExecutionRoots(commandStoreSlot.prepared().records()),
|
|
237
|
-
...threads.preparedExecutionRoots(),
|
|
238
|
-
],
|
|
239
|
-
normalPlane: {
|
|
240
|
-
requested: () => shutdownRuntime.requested(),
|
|
241
|
-
armQuotaPolling: () => quotaPoller.arm(),
|
|
242
|
-
beginPidSnapshots: () => lifecycle.beginPidSnapshots(),
|
|
243
|
-
migrateAccounts: () => runStartupAccountsMigration(threads, quotaStoreSlot.current(), (m) => logLine(logPath(), redactSecrets(m))),
|
|
244
|
-
startSetup: () => setupBinding.start(),
|
|
245
|
-
quarantineGhosts: () => quarantineGhostProjectsAtStartup(threads, (message) => logLine(logPath(), message)),
|
|
246
|
-
scheduleRetention: () => scheduleStartupRetention(services.runRetention, {
|
|
247
|
-
logPath: logPath(),
|
|
248
|
-
shuttingDown: () => shutdownRuntime.requested(),
|
|
249
|
-
}),
|
|
250
|
-
pruneCommandHistory: () => server.pruneHistory(),
|
|
251
|
-
armJournalMaintenance: () => journalMaintenance.arm(),
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
services.recoveryQuarantinePartition = wrapQuarantineWithReopen(services.recoveryQuarantinePartition);
|
|
255
|
-
// D5 stage 3: bind the REAL transport with product admission CLOSED, then
|
|
256
|
-
// prove self-health/exact identity through it before anything destructive.
|
|
257
|
-
const controlAddr = await bindRecoveryTransport({
|
|
263
|
+
address = await bindRecoveryTransport({
|
|
258
264
|
server,
|
|
259
265
|
control,
|
|
260
|
-
requested: () =>
|
|
266
|
+
requested: () => shutdown.requested(),
|
|
261
267
|
daemonDir: daemonDir(),
|
|
262
268
|
logPath: logPath(),
|
|
263
269
|
socketPath,
|
|
264
270
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
271
|
+
listening = !shutdown.requested();
|
|
272
|
+
async function completeAdmission() {
|
|
273
|
+
if (admissionFlight)
|
|
274
|
+
return admissionFlight;
|
|
275
|
+
if (shutdown.requested() || admission.snapshot() === "normal")
|
|
276
|
+
return;
|
|
277
|
+
const value = graph();
|
|
278
|
+
const blocked = storage.blockedPartitions();
|
|
279
|
+
if (blocked.length) {
|
|
280
|
+
admission.enterRecoveryOnly();
|
|
281
|
+
log(`recovery required: ${blocked.join(", ")}`);
|
|
282
|
+
await ensureRecoveryControl();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const work = async () => {
|
|
286
|
+
if (!listening) {
|
|
287
|
+
await server.start();
|
|
288
|
+
listening = true;
|
|
289
|
+
await proveRecoveryTransport({ socketPath, identity, token, control: address });
|
|
290
|
+
}
|
|
291
|
+
if (dutiesFor !== value) {
|
|
292
|
+
await value.recoverAfterStartup();
|
|
293
|
+
const roots = sqlStartupRoots(value.store);
|
|
294
|
+
await runStartupCrashGc({
|
|
295
|
+
daemonDir: daemonDir(),
|
|
296
|
+
logPath: logPath(),
|
|
297
|
+
knownProjectRoots: roots.projects,
|
|
298
|
+
knownExecutionRoots: roots.execution,
|
|
299
|
+
...(diagnostics.diagnostics ? { diagnostics: diagnostics.diagnostics } : {}),
|
|
300
|
+
});
|
|
301
|
+
if (shutdown.requested())
|
|
302
|
+
return;
|
|
303
|
+
for (const sweep of sweepRetiredConfigKeysAtStartup())
|
|
304
|
+
log(`swept retired config keys from ${sweep.path}: ${sweep.removed.join(", ")}`);
|
|
305
|
+
runStartupAccountsMigration(value.threads, value.quota, (message) => log(redactSecrets(message)));
|
|
306
|
+
server.pruneHistory();
|
|
307
|
+
await setup.start();
|
|
308
|
+
quarantineGhostProjectsAtStartup(value.threads, log);
|
|
309
|
+
if (shutdown.requested())
|
|
310
|
+
return;
|
|
311
|
+
dutiesFor = value;
|
|
312
|
+
}
|
|
313
|
+
admission.openNormal();
|
|
314
|
+
recordAdmissionMemory();
|
|
315
|
+
quotaPoller.arm();
|
|
316
|
+
lifecycle.beginPidSnapshots();
|
|
317
|
+
scheduleStartupRetention(services.runRetention, {
|
|
318
|
+
logPath: logPath(),
|
|
319
|
+
shuttingDown: () => shutdown.requested() || admission.snapshot() !== "normal",
|
|
320
|
+
});
|
|
321
|
+
void value.maintenance
|
|
322
|
+
.integrityCheck()
|
|
323
|
+
.then(async (result) => {
|
|
324
|
+
if (result.ok && admission.snapshot() === "normal")
|
|
325
|
+
await value.maintenance.sweepOrphans();
|
|
326
|
+
})
|
|
327
|
+
.catch((error) => log(`store maintenance: ${String(error)}`));
|
|
328
|
+
log(`startup admission: normal product admission open (${processMemoryFields()})`);
|
|
329
|
+
};
|
|
330
|
+
admissionFlight = work();
|
|
331
|
+
try {
|
|
332
|
+
await admissionFlight;
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
admissionFlight = null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (!shutdown.requested()) {
|
|
339
|
+
await proveRecoveryTransport({ socketPath, identity, token, control: address });
|
|
340
|
+
opening = storage.open(abort.signal).then(completeAdmission);
|
|
341
|
+
try {
|
|
342
|
+
await opening;
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
admission.enterRecoveryOnly();
|
|
346
|
+
log(`SQL startup remains recovery-only: ${redactSecrets(String(error))}`);
|
|
347
|
+
diagnostics.recordFailure("SQL startup remains recovery-only", error);
|
|
348
|
+
if (!shutdown.requested())
|
|
349
|
+
await ensureRecoveryControl();
|
|
350
|
+
}
|
|
276
351
|
}
|
|
277
|
-
await
|
|
278
|
-
lifecycle.finalize();
|
|
279
|
-
|
|
280
|
-
|
|
352
|
+
await shutdown.wait();
|
|
353
|
+
await lifecycle.finalize();
|
|
354
|
+
log("claudexord shut down");
|
|
355
|
+
diagnostics.recordStage("shutdown_complete", "claudexord shut down");
|
|
281
356
|
}
|
|
282
357
|
catch (error) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
startupDiagnostics.recordFailure("daemon lifecycle FAILED", error);
|
|
287
|
-
if (shutdownRuntime) {
|
|
358
|
+
log(`daemon lifecycle FAILED: ${redactSecrets(String(error))}`);
|
|
359
|
+
diagnostics.recordFailure("daemon lifecycle FAILED", error);
|
|
360
|
+
if (shutdown) {
|
|
288
361
|
try {
|
|
289
|
-
await
|
|
290
|
-
lifecycle?.finalize();
|
|
362
|
+
await shutdown.beginShutdown("startup failure");
|
|
363
|
+
await lifecycle?.finalize();
|
|
291
364
|
}
|
|
292
365
|
catch (shutdownError) {
|
|
293
|
-
|
|
294
|
-
releaseWriterLease = false;
|
|
366
|
+
cleanShutdown = false;
|
|
295
367
|
throw new AggregateError([error, shutdownError], "claudexord failed and could not complete shutdown");
|
|
296
368
|
}
|
|
297
369
|
}
|
|
298
370
|
throw error;
|
|
299
371
|
}
|
|
300
372
|
finally {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (releaseWriterLease)
|
|
306
|
-
rootAuthority.release();
|
|
373
|
+
quotaPoller.stop();
|
|
374
|
+
diagnostics.close();
|
|
375
|
+
if (cleanShutdown)
|
|
376
|
+
authority.release();
|
|
307
377
|
}
|
|
308
378
|
}
|
|
309
|
-
/** Explicit entry preserves import-side-effect freedom for daemon probes. */
|
|
310
379
|
export function runClaudexordEntry() {
|
|
311
380
|
dispatchClaudexordEntry(main);
|
|
312
381
|
}
|