@pstdio/pocketcoder-cli 0.2.0 → 0.3.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/index.js +1638 -155
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -366,7 +366,7 @@ var require_main = __commonJS((exports, module) => {
|
|
|
366
366
|
});
|
|
367
367
|
|
|
368
368
|
// src/index.ts
|
|
369
|
-
import { randomUUID as
|
|
369
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
370
370
|
import { existsSync as existsSync2, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
371
371
|
import { dirname as dirname7, join as join6, resolve as resolve8 } from "path";
|
|
372
372
|
|
|
@@ -453,80 +453,6 @@ function sha256Hex(input) {
|
|
|
453
453
|
function digestOf(value) {
|
|
454
454
|
return `sha256:${sha256Hex(canonicalJson(value))}`;
|
|
455
455
|
}
|
|
456
|
-
// ../contracts/src/duration.ts
|
|
457
|
-
var DURATION_RE = /^(\d+)(ms|s|m|h)$/;
|
|
458
|
-
var UNIT_MS = {
|
|
459
|
-
ms: 1,
|
|
460
|
-
s: 1000,
|
|
461
|
-
m: 60000,
|
|
462
|
-
h: 3600000
|
|
463
|
-
};
|
|
464
|
-
function isDuration(value) {
|
|
465
|
-
return DURATION_RE.test(value);
|
|
466
|
-
}
|
|
467
|
-
function parseDurationMs(value) {
|
|
468
|
-
const match = DURATION_RE.exec(value);
|
|
469
|
-
if (!match) {
|
|
470
|
-
throw new Error(`invalid duration: ${value}`);
|
|
471
|
-
}
|
|
472
|
-
const amount = Number(match[1]);
|
|
473
|
-
const unit = UNIT_MS[match[2]];
|
|
474
|
-
if (unit === undefined) {
|
|
475
|
-
throw new Error(`invalid duration unit: ${value}`);
|
|
476
|
-
}
|
|
477
|
-
return amount * unit;
|
|
478
|
-
}
|
|
479
|
-
// ../contracts/src/errors.ts
|
|
480
|
-
var ERROR_CODES = {
|
|
481
|
-
"auth.invalid_key": 401,
|
|
482
|
-
"auth.missing_scope": 403,
|
|
483
|
-
"auth.disabled_principal": 403,
|
|
484
|
-
"validation.invalid": 400,
|
|
485
|
-
"idempotency.conflict": 409,
|
|
486
|
-
"capacity.queue_full": 429,
|
|
487
|
-
"capacity.waiters_full": 429,
|
|
488
|
-
"template.not_found": 404,
|
|
489
|
-
"template.version_not_found": 404,
|
|
490
|
-
"template.not_authorized": 403,
|
|
491
|
-
"workspace.not_found": 404,
|
|
492
|
-
"workspace.not_ready": 409,
|
|
493
|
-
"workspace.terminal": 410,
|
|
494
|
-
"workspace.disconnected": 503,
|
|
495
|
-
"workspace.persistence_not_enabled": 409,
|
|
496
|
-
"workspace.preserving": 409,
|
|
497
|
-
"checkpoint.not_found": 404,
|
|
498
|
-
"checkpoint.not_ready": 409,
|
|
499
|
-
"checkpoint.none_ready": 409,
|
|
500
|
-
"checkpoint.corrupt": 409,
|
|
501
|
-
"checkpoint.quota_exceeded": 413,
|
|
502
|
-
"checkpoint.in_use": 409,
|
|
503
|
-
"restore.template_not_authorized": 403,
|
|
504
|
-
"restore.image_unavailable": 409,
|
|
505
|
-
"restore.incompatible": 409,
|
|
506
|
-
"operation.conflict": 409,
|
|
507
|
-
"source.not_allowed": 422,
|
|
508
|
-
"source.invalid_revision": 422,
|
|
509
|
-
"secret.unavailable": 503,
|
|
510
|
-
"storage.capacity_exhausted": 507,
|
|
511
|
-
"relay.body_too_large": 413,
|
|
512
|
-
"relay.route_not_allowed": 422,
|
|
513
|
-
"relay.deadline_exceeded": 504,
|
|
514
|
-
"relay.upstream_error": 502,
|
|
515
|
-
"internal.error": 500
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
class ApiError extends Error {
|
|
519
|
-
code;
|
|
520
|
-
status;
|
|
521
|
-
constructor(code, message) {
|
|
522
|
-
super(message);
|
|
523
|
-
this.code = code;
|
|
524
|
-
this.status = ERROR_CODES[code];
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
function errorEnvelope(code, message, requestId) {
|
|
528
|
-
return { error: { code, message, request_id: requestId } };
|
|
529
|
-
}
|
|
530
456
|
// ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
531
457
|
var exports_external = {};
|
|
532
458
|
__export(exports_external, {
|
|
@@ -14803,6 +14729,108 @@ function date4(params) {
|
|
|
14803
14729
|
|
|
14804
14730
|
// ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
14805
14731
|
config(en_default());
|
|
14732
|
+
// ../contracts/src/conversation.ts
|
|
14733
|
+
var CONVERSATION_ROLES = ["user", "assistant", "system", "tool"];
|
|
14734
|
+
var ConversationMetadataSchema = exports_external.record(exports_external.string().min(1).max(64), exports_external.string().max(512)).refine((value) => Object.keys(value).length <= 32, "conversation metadata has at most 32 keys");
|
|
14735
|
+
var ConversationMessageInputSchema = exports_external.object({
|
|
14736
|
+
message_id: exports_external.string().min(1).max(256),
|
|
14737
|
+
role: exports_external.enum(CONVERSATION_ROLES),
|
|
14738
|
+
content: exports_external.string().max(256 * 1024),
|
|
14739
|
+
occurred_at: exports_external.iso.datetime(),
|
|
14740
|
+
metadata: ConversationMetadataSchema.default({})
|
|
14741
|
+
});
|
|
14742
|
+
var ConversationMessageResourceSchema = ConversationMessageInputSchema.extend({
|
|
14743
|
+
seq: exports_external.number().int().positive()
|
|
14744
|
+
});
|
|
14745
|
+
var ConversationListQuerySchema = exports_external.object({
|
|
14746
|
+
after: exports_external.coerce.number().int().nonnegative().default(0),
|
|
14747
|
+
limit: exports_external.coerce.number().int().positive().max(200).default(100)
|
|
14748
|
+
});
|
|
14749
|
+
var ConversationResumeOutcomeSchema = exports_external.object({
|
|
14750
|
+
status: exports_external.literal("supported"),
|
|
14751
|
+
reason: exports_external.null(),
|
|
14752
|
+
source_workspace_id: exports_external.uuid(),
|
|
14753
|
+
checkpoint_id: exports_external.uuid()
|
|
14754
|
+
});
|
|
14755
|
+
// ../contracts/src/duration.ts
|
|
14756
|
+
var DURATION_RE = /^(\d+)(ms|s|m|h)$/;
|
|
14757
|
+
var UNIT_MS = {
|
|
14758
|
+
ms: 1,
|
|
14759
|
+
s: 1000,
|
|
14760
|
+
m: 60000,
|
|
14761
|
+
h: 3600000
|
|
14762
|
+
};
|
|
14763
|
+
function isDuration(value) {
|
|
14764
|
+
return DURATION_RE.test(value);
|
|
14765
|
+
}
|
|
14766
|
+
function parseDurationMs(value) {
|
|
14767
|
+
const match = DURATION_RE.exec(value);
|
|
14768
|
+
if (!match) {
|
|
14769
|
+
throw new Error(`invalid duration: ${value}`);
|
|
14770
|
+
}
|
|
14771
|
+
const amount = Number(match[1]);
|
|
14772
|
+
const unit = UNIT_MS[match[2]];
|
|
14773
|
+
if (unit === undefined) {
|
|
14774
|
+
throw new Error(`invalid duration unit: ${value}`);
|
|
14775
|
+
}
|
|
14776
|
+
return amount * unit;
|
|
14777
|
+
}
|
|
14778
|
+
// ../contracts/src/errors.ts
|
|
14779
|
+
var ERROR_CODES = {
|
|
14780
|
+
"auth.invalid_key": 401,
|
|
14781
|
+
"auth.missing_scope": 403,
|
|
14782
|
+
"auth.disabled_principal": 403,
|
|
14783
|
+
"validation.invalid": 400,
|
|
14784
|
+
"idempotency.conflict": 409,
|
|
14785
|
+
"capacity.queue_full": 429,
|
|
14786
|
+
"capacity.waiters_full": 429,
|
|
14787
|
+
"template.not_found": 404,
|
|
14788
|
+
"template.version_not_found": 404,
|
|
14789
|
+
"template.not_authorized": 403,
|
|
14790
|
+
"workspace.not_found": 404,
|
|
14791
|
+
"workspace.not_ready": 409,
|
|
14792
|
+
"workspace.terminal": 410,
|
|
14793
|
+
"workspace.disconnected": 503,
|
|
14794
|
+
"workspace.persistence_not_enabled": 409,
|
|
14795
|
+
"workspace.preserving": 409,
|
|
14796
|
+
"checkpoint.not_found": 404,
|
|
14797
|
+
"checkpoint.not_ready": 409,
|
|
14798
|
+
"checkpoint.none_ready": 409,
|
|
14799
|
+
"checkpoint.corrupt": 409,
|
|
14800
|
+
"checkpoint.quota_exceeded": 413,
|
|
14801
|
+
"checkpoint.in_use": 409,
|
|
14802
|
+
"restore.template_not_authorized": 403,
|
|
14803
|
+
"restore.image_unavailable": 409,
|
|
14804
|
+
"restore.incompatible": 409,
|
|
14805
|
+
"resume.unsupported": 409,
|
|
14806
|
+
"conversation.expired": 410,
|
|
14807
|
+
"conversation.deleted": 410,
|
|
14808
|
+
"operation.conflict": 409,
|
|
14809
|
+
"source.not_allowed": 422,
|
|
14810
|
+
"source.invalid_revision": 422,
|
|
14811
|
+
"secret.unavailable": 503,
|
|
14812
|
+
"storage.capacity_exhausted": 507,
|
|
14813
|
+
"relay.body_too_large": 413,
|
|
14814
|
+
"relay.route_not_allowed": 422,
|
|
14815
|
+
"relay.deadline_exceeded": 504,
|
|
14816
|
+
"relay.upstream_error": 502,
|
|
14817
|
+
"internal.error": 500
|
|
14818
|
+
};
|
|
14819
|
+
|
|
14820
|
+
class ApiError extends Error {
|
|
14821
|
+
code;
|
|
14822
|
+
status;
|
|
14823
|
+
details;
|
|
14824
|
+
constructor(code, message, details) {
|
|
14825
|
+
super(message);
|
|
14826
|
+
this.code = code;
|
|
14827
|
+
this.status = ERROR_CODES[code];
|
|
14828
|
+
this.details = details;
|
|
14829
|
+
}
|
|
14830
|
+
}
|
|
14831
|
+
function errorEnvelope(code, message, requestId, details) {
|
|
14832
|
+
return { error: { code, message, request_id: requestId, ...details ? { details } : {} } };
|
|
14833
|
+
}
|
|
14806
14834
|
// ../contracts/src/persistence.ts
|
|
14807
14835
|
var DurationValueSchema = exports_external.string().refine(isDuration, { message: "expected a duration like 15s, 20m, or 2h" });
|
|
14808
14836
|
var LAUNCH_MODES = ["create", "restore"];
|
|
@@ -14841,6 +14869,7 @@ var CheckpointPolicySchema = exports_external.object({
|
|
|
14841
14869
|
var PersistenceSpecSchema = exports_external.object({
|
|
14842
14870
|
mounts: exports_external.array(PersistenceMountSchema).max(16).default([]),
|
|
14843
14871
|
conversationRestore: exports_external.enum(CONVERSATION_RESTORE_CAPABILITIES).default("filesystem_only"),
|
|
14872
|
+
conversationRetention: DurationValueSchema.default("168h"),
|
|
14844
14873
|
sessionCompatibility: exports_external.string().min(1).max(128).optional(),
|
|
14845
14874
|
checkpoint: CheckpointPolicySchema.prefault({})
|
|
14846
14875
|
});
|
|
@@ -15016,6 +15045,7 @@ var WorkspaceResourceSchema = exports_external.object({
|
|
|
15016
15045
|
agent_state: exports_external.enum(AGENT_STATES),
|
|
15017
15046
|
change_cursor: exports_external.number().int().nonnegative(),
|
|
15018
15047
|
provider_kind: exports_external.string().nullable(),
|
|
15048
|
+
provisioning_mode: exports_external.enum(["cold", "warm"]).nullable(),
|
|
15019
15049
|
health: exports_external.record(exports_external.string(), exports_external.string()),
|
|
15020
15050
|
created_at: exports_external.iso.datetime(),
|
|
15021
15051
|
updated_at: exports_external.iso.datetime(),
|
|
@@ -15033,6 +15063,10 @@ var WorkspaceResourceSchema = exports_external.object({
|
|
|
15033
15063
|
persistence: exports_external.object({
|
|
15034
15064
|
enabled: exports_external.boolean(),
|
|
15035
15065
|
conversation_restore: exports_external.enum(CONVERSATION_RESTORE_CAPABILITIES),
|
|
15066
|
+
conversation_resume: exports_external.object({
|
|
15067
|
+
status: exports_external.enum(["supported", "unsupported", "unknown"]),
|
|
15068
|
+
reason: exports_external.enum(["filesystem_only", "capability_unknown"]).nullable()
|
|
15069
|
+
}),
|
|
15036
15070
|
latest_checkpoint_id: exports_external.uuid().nullable()
|
|
15037
15071
|
}),
|
|
15038
15072
|
outputs: exports_external.record(exports_external.string(), exports_external.unknown()),
|
|
@@ -15050,6 +15084,9 @@ var WorkspaceListQuerySchema = exports_external.object({
|
|
|
15050
15084
|
external_id: exports_external.string().optional(),
|
|
15051
15085
|
state: exports_external.enum(WORKSPACE_STATES).optional(),
|
|
15052
15086
|
template: exports_external.string().optional(),
|
|
15087
|
+
metadata: exports_external.string().max(4096).optional(),
|
|
15088
|
+
created_after: exports_external.iso.datetime().optional(),
|
|
15089
|
+
created_before: exports_external.iso.datetime().optional(),
|
|
15053
15090
|
limit: exports_external.coerce.number().int().positive().max(200).default(50),
|
|
15054
15091
|
cursor: exports_external.string().optional()
|
|
15055
15092
|
});
|
|
@@ -15073,6 +15110,7 @@ var EventEnvelopeSchema = exports_external.object({
|
|
|
15073
15110
|
state: exports_external.enum(WORKSPACE_STATES),
|
|
15074
15111
|
reason_code: exports_external.string().nullable(),
|
|
15075
15112
|
agent_state: exports_external.enum(AGENT_STATES),
|
|
15113
|
+
provisioning_mode: exports_external.enum(["cold", "warm"]).nullable().default(null),
|
|
15076
15114
|
change_cursor: exports_external.number().int().nonnegative(),
|
|
15077
15115
|
failure: exports_external.object({
|
|
15078
15116
|
reason_code: exports_external.enum(REASON_CODES),
|
|
@@ -15489,12 +15527,15 @@ function findRoute(snapshot, service, method, path) {
|
|
|
15489
15527
|
// ../contracts/src/protocol.ts
|
|
15490
15528
|
var LEGACY_PROTOCOL_VERSION = 1;
|
|
15491
15529
|
var PROTOCOL_VERSION = 2;
|
|
15530
|
+
var POOL_PROTOCOL_VERSION = 3;
|
|
15492
15531
|
var SUPPORTED_PROTOCOL_VERSIONS = [LEGACY_PROTOCOL_VERSION, PROTOCOL_VERSION];
|
|
15493
15532
|
var MAX_FRAME_BYTES = 1048576;
|
|
15494
15533
|
var HEADER_PROTOCOL = "x-pocketcoder-protocol";
|
|
15495
15534
|
var HEADER_WORKSPACE = "x-pocketcoder-workspace";
|
|
15496
15535
|
var HEADER_REGISTRATION = "x-pocketcoder-registration";
|
|
15497
15536
|
var HEADER_RECONNECT = "x-pocketcoder-reconnect";
|
|
15537
|
+
var HEADER_POOL_RUNTIME = "x-pocketcoder-pool-runtime";
|
|
15538
|
+
var HEADER_POOL_ENROLLMENT = "x-pocketcoder-pool-enrollment";
|
|
15498
15539
|
var EnvelopeBase = exports_external.object({
|
|
15499
15540
|
v: exports_external.union([exports_external.literal(LEGACY_PROTOCOL_VERSION), exports_external.literal(PROTOCOL_VERSION)]),
|
|
15500
15541
|
workspace_id: exports_external.uuid(),
|
|
@@ -15565,6 +15606,7 @@ var RestoreStatusPayload = exports_external.object({
|
|
|
15565
15606
|
capability: exports_external.enum(CONVERSATION_RESTORE_CAPABILITIES),
|
|
15566
15607
|
detail: exports_external.string().max(512).optional()
|
|
15567
15608
|
});
|
|
15609
|
+
var ConversationMessagePayload = ConversationMessageInputSchema;
|
|
15568
15610
|
var AgentFrameSchema = exports_external.discriminatedUnion("type", [
|
|
15569
15611
|
EnvelopeBase.extend({ type: exports_external.literal("registered"), payload: RegisteredPayload }),
|
|
15570
15612
|
EnvelopeBase.extend({ type: exports_external.literal("heartbeat"), payload: HeartbeatPayload }),
|
|
@@ -15577,7 +15619,11 @@ var AgentFrameSchema = exports_external.discriminatedUnion("type", [
|
|
|
15577
15619
|
EnvelopeBase.extend({ type: exports_external.literal("source_resolved"), payload: SourceResolvedPayload }),
|
|
15578
15620
|
EnvelopeBase.extend({ type: exports_external.literal("checkpoint_status"), payload: CheckpointStatusPayload }),
|
|
15579
15621
|
EnvelopeBase.extend({ type: exports_external.literal("output_published"), payload: OutputPublishedPayload }),
|
|
15580
|
-
EnvelopeBase.extend({ type: exports_external.literal("restore_status"), payload: RestoreStatusPayload })
|
|
15622
|
+
EnvelopeBase.extend({ type: exports_external.literal("restore_status"), payload: RestoreStatusPayload }),
|
|
15623
|
+
EnvelopeBase.extend({
|
|
15624
|
+
type: exports_external.literal("conversation_message"),
|
|
15625
|
+
payload: ConversationMessagePayload
|
|
15626
|
+
})
|
|
15581
15627
|
]);
|
|
15582
15628
|
var ExecSpecSchema = exports_external.object({
|
|
15583
15629
|
setup: exports_external.array(SetupStepSchema),
|
|
@@ -15670,6 +15716,27 @@ var ProviderInputSchema = exports_external.object({
|
|
|
15670
15716
|
}).optional(),
|
|
15671
15717
|
launch_input: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
15672
15718
|
});
|
|
15719
|
+
var PoolProviderInputSchema = exports_external.object({
|
|
15720
|
+
pool_runtime_id: exports_external.uuid(),
|
|
15721
|
+
server_url: exports_external.string(),
|
|
15722
|
+
enrollment_secret: exports_external.string().min(1),
|
|
15723
|
+
template_digest: exports_external.string(),
|
|
15724
|
+
template_name: exports_external.string(),
|
|
15725
|
+
template_version: exports_external.string()
|
|
15726
|
+
});
|
|
15727
|
+
var ProviderBootstrapInputSchema = exports_external.union([ProviderInputSchema, PoolProviderInputSchema]);
|
|
15728
|
+
var PoolRegisteredFrameSchema = exports_external.object({
|
|
15729
|
+
v: exports_external.literal(POOL_PROTOCOL_VERSION),
|
|
15730
|
+
type: exports_external.literal("pool_registered"),
|
|
15731
|
+
pool_runtime_id: exports_external.uuid(),
|
|
15732
|
+
template: exports_external.object({ name: exports_external.string(), version: exports_external.string(), digest: exports_external.string() }),
|
|
15733
|
+
agent_version: exports_external.string()
|
|
15734
|
+
});
|
|
15735
|
+
var LeaseAssignmentFrameSchema = exports_external.object({
|
|
15736
|
+
v: exports_external.literal(POOL_PROTOCOL_VERSION),
|
|
15737
|
+
type: exports_external.literal("lease_assignment"),
|
|
15738
|
+
input: ProviderInputSchema
|
|
15739
|
+
});
|
|
15673
15740
|
// ../contracts/src/scopes.ts
|
|
15674
15741
|
var SCOPES = [
|
|
15675
15742
|
"templates:read",
|
|
@@ -15681,6 +15748,8 @@ var SCOPES = [
|
|
|
15681
15748
|
"checkpoints:read",
|
|
15682
15749
|
"checkpoints:delete",
|
|
15683
15750
|
"outputs:read",
|
|
15751
|
+
"conversations:read",
|
|
15752
|
+
"conversations:delete",
|
|
15684
15753
|
"services:relay",
|
|
15685
15754
|
"logs:read",
|
|
15686
15755
|
"admin"
|
|
@@ -31118,6 +31187,58 @@ ALTER TABLE "workspaces" ADD COLUMN "failure_log_tail" text;--> statement-breakp
|
|
|
31118
31187
|
ALTER TABLE "workspaces" ADD COLUMN "failure_log_tail_truncated" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
|
31119
31188
|
ALTER TABLE "workspaces" ADD COLUMN "failure_last_log_seq" bigint;`;
|
|
31120
31189
|
|
|
31190
|
+
// ../db/drizzle/20260803081501_warm-workspace-pool/migration.sql
|
|
31191
|
+
var migration_default4 = `CREATE TABLE "warm_pool_runtimes" (
|
|
31192
|
+
"id" uuid PRIMARY KEY,
|
|
31193
|
+
"template_id" uuid NOT NULL,
|
|
31194
|
+
"template_name" text NOT NULL,
|
|
31195
|
+
"template_version" text NOT NULL,
|
|
31196
|
+
"template_digest" text NOT NULL,
|
|
31197
|
+
"driver_kind" text NOT NULL,
|
|
31198
|
+
"eligibility_fingerprint" text NOT NULL,
|
|
31199
|
+
"state" text NOT NULL,
|
|
31200
|
+
"provider_ref" jsonb,
|
|
31201
|
+
"enrollment_digest" bytea,
|
|
31202
|
+
"enrollment_expires_at" timestamp with time zone,
|
|
31203
|
+
"workspace_id" uuid,
|
|
31204
|
+
"created_at" timestamp with time zone NOT NULL,
|
|
31205
|
+
"updated_at" timestamp with time zone NOT NULL,
|
|
31206
|
+
"ready_at" timestamp with time zone,
|
|
31207
|
+
"leased_at" timestamp with time zone,
|
|
31208
|
+
"failure_code" text
|
|
31209
|
+
);
|
|
31210
|
+
--> statement-breakpoint
|
|
31211
|
+
ALTER TABLE "workspaces" ADD COLUMN "provisioning_mode" text;--> statement-breakpoint
|
|
31212
|
+
CREATE INDEX "warm_pool_eligible" ON "warm_pool_runtimes" ("template_digest","driver_kind","eligibility_fingerprint","state","ready_at");--> statement-breakpoint
|
|
31213
|
+
CREATE UNIQUE INDEX "warm_pool_workspace_once" ON "warm_pool_runtimes" ("workspace_id");--> statement-breakpoint
|
|
31214
|
+
ALTER TABLE "warm_pool_runtimes" ADD CONSTRAINT "warm_pool_runtimes_template_id_templates_id_fkey" FOREIGN KEY ("template_id") REFERENCES "templates"("id");--> statement-breakpoint
|
|
31215
|
+
ALTER TABLE "warm_pool_runtimes" ADD CONSTRAINT "warm_pool_runtimes_workspace_id_workspaces_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id");`;
|
|
31216
|
+
|
|
31217
|
+
// ../db/drizzle/20260803092834_durable-conversations/migration.sql
|
|
31218
|
+
var migration_default5 = `CREATE TABLE "workspace_conversation_messages" (
|
|
31219
|
+
"workspace_id" uuid,
|
|
31220
|
+
"seq" bigint,
|
|
31221
|
+
"message_id" text NOT NULL,
|
|
31222
|
+
"role" text NOT NULL,
|
|
31223
|
+
"content" text NOT NULL,
|
|
31224
|
+
"occurred_at" timestamp with time zone NOT NULL,
|
|
31225
|
+
"metadata" jsonb DEFAULT '{}' NOT NULL,
|
|
31226
|
+
"created_at" timestamp with time zone NOT NULL,
|
|
31227
|
+
CONSTRAINT "workspace_conversation_messages_pkey" PRIMARY KEY("workspace_id","seq"),
|
|
31228
|
+
CONSTRAINT "workspace_conversation_messages_workspace_id_message_id_unique" UNIQUE("workspace_id","message_id")
|
|
31229
|
+
);
|
|
31230
|
+
--> statement-breakpoint
|
|
31231
|
+
CREATE TABLE "workspace_conversations" (
|
|
31232
|
+
"workspace_id" uuid PRIMARY KEY,
|
|
31233
|
+
"status" text NOT NULL,
|
|
31234
|
+
"expires_at" timestamp with time zone,
|
|
31235
|
+
"deleted_at" timestamp with time zone,
|
|
31236
|
+
"updated_at" timestamp with time zone NOT NULL
|
|
31237
|
+
);
|
|
31238
|
+
--> statement-breakpoint
|
|
31239
|
+
ALTER TABLE "workspace_conversation_messages" ADD CONSTRAINT "workspace_conversation_messages_workspace_id_workspaces_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id");--> statement-breakpoint
|
|
31240
|
+
ALTER TABLE "workspace_conversations" ADD CONSTRAINT "workspace_conversations_workspace_id_workspaces_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id");`;
|
|
31241
|
+
|
|
31121
31242
|
// ../db/src/migrations.ts
|
|
31122
31243
|
function migration(name2, folderMillis, source) {
|
|
31123
31244
|
return {
|
|
@@ -31131,7 +31252,9 @@ function migration(name2, folderMillis, source) {
|
|
|
31131
31252
|
var MIGRATIONS = [
|
|
31132
31253
|
migration("20260730103433_initial", Date.UTC(2026, 6, 30, 10, 34, 33), migration_default),
|
|
31133
31254
|
migration("20260730113243_regular_pretty_boy", Date.UTC(2026, 6, 30, 11, 32, 43), migration_default2),
|
|
31134
|
-
migration("20260730132750_workspace-change-diagnostics", Date.UTC(2026, 6, 30, 13, 27, 50), migration_default3)
|
|
31255
|
+
migration("20260730132750_workspace-change-diagnostics", Date.UTC(2026, 6, 30, 13, 27, 50), migration_default3),
|
|
31256
|
+
migration("20260803081501_warm-workspace-pool", Date.UTC(2026, 7, 3, 8, 15, 1), migration_default4),
|
|
31257
|
+
migration("20260803092834_durable-conversations", Date.UTC(2026, 7, 3, 9, 28, 34), migration_default5)
|
|
31135
31258
|
];
|
|
31136
31259
|
|
|
31137
31260
|
// ../db/src/schema.ts
|
|
@@ -31190,6 +31313,7 @@ var workspaces = pgTable("workspaces", {
|
|
|
31190
31313
|
launchInput: jsonb("launch_input").$type(),
|
|
31191
31314
|
providerKind: text2("provider_kind"),
|
|
31192
31315
|
providerRef: jsonb("provider_ref").$type(),
|
|
31316
|
+
provisioningMode: text2("provisioning_mode"),
|
|
31193
31317
|
registrationDigest: bytea2("registration_digest"),
|
|
31194
31318
|
registrationExpiresAt: timestamptz("registration_expires_at"),
|
|
31195
31319
|
reconnectDigest: bytea2("reconnect_digest"),
|
|
@@ -31219,6 +31343,28 @@ var workspaces = pgTable("workspaces", {
|
|
|
31219
31343
|
index("workspaces_state_created").on(table.state, table.createdAt),
|
|
31220
31344
|
index("workspaces_deadline").on(table.deadlineAt).where(sql`${table.state} NOT IN ${terminalStates}`)
|
|
31221
31345
|
]);
|
|
31346
|
+
var warmPoolRuntimes = pgTable("warm_pool_runtimes", {
|
|
31347
|
+
id: uuid3("id").primaryKey(),
|
|
31348
|
+
templateId: uuid3("template_id").notNull().references(() => templates.id),
|
|
31349
|
+
templateName: text2("template_name").notNull(),
|
|
31350
|
+
templateVersion: text2("template_version").notNull(),
|
|
31351
|
+
templateDigest: text2("template_digest").notNull(),
|
|
31352
|
+
driverKind: text2("driver_kind").notNull(),
|
|
31353
|
+
eligibilityFingerprint: text2("eligibility_fingerprint").notNull(),
|
|
31354
|
+
state: text2("state").notNull(),
|
|
31355
|
+
providerRef: jsonb("provider_ref").$type(),
|
|
31356
|
+
enrollmentDigest: bytea2("enrollment_digest"),
|
|
31357
|
+
enrollmentExpiresAt: timestamptz("enrollment_expires_at"),
|
|
31358
|
+
workspaceId: uuid3("workspace_id").references(() => workspaces.id),
|
|
31359
|
+
createdAt: timestamptz("created_at").notNull(),
|
|
31360
|
+
updatedAt: timestamptz("updated_at").notNull(),
|
|
31361
|
+
readyAt: timestamptz("ready_at"),
|
|
31362
|
+
leasedAt: timestamptz("leased_at"),
|
|
31363
|
+
failureCode: text2("failure_code")
|
|
31364
|
+
}, (table) => [
|
|
31365
|
+
index("warm_pool_eligible").on(table.templateDigest, table.driverKind, table.eligibilityFingerprint, table.state, table.readyAt),
|
|
31366
|
+
uniqueIndex("warm_pool_workspace_once").on(table.workspaceId)
|
|
31367
|
+
]);
|
|
31222
31368
|
var workspaceStorage = pgTable("workspace_storage", {
|
|
31223
31369
|
id: uuid3("id").primaryKey(),
|
|
31224
31370
|
workspaceId: uuid3("workspace_id").notNull().references(() => workspaces.id),
|
|
@@ -31294,6 +31440,26 @@ var workspaceOutputs = pgTable("workspace_outputs", {
|
|
|
31294
31440
|
value: jsonb("value").$type().notNull(),
|
|
31295
31441
|
occurredAt: timestamptz("occurred_at").notNull()
|
|
31296
31442
|
}, (table) => [primaryKey({ columns: [table.workspaceId, table.seq] })]);
|
|
31443
|
+
var workspaceConversations = pgTable("workspace_conversations", {
|
|
31444
|
+
workspaceId: uuid3("workspace_id").primaryKey().references(() => workspaces.id),
|
|
31445
|
+
status: text2("status").notNull(),
|
|
31446
|
+
expiresAt: timestamptz("expires_at"),
|
|
31447
|
+
deletedAt: timestamptz("deleted_at"),
|
|
31448
|
+
updatedAt: timestamptz("updated_at").notNull()
|
|
31449
|
+
});
|
|
31450
|
+
var workspaceConversationMessages = pgTable("workspace_conversation_messages", {
|
|
31451
|
+
workspaceId: uuid3("workspace_id").notNull().references(() => workspaces.id),
|
|
31452
|
+
seq: bigint5("seq", { mode: "bigint" }).notNull(),
|
|
31453
|
+
messageId: text2("message_id").notNull(),
|
|
31454
|
+
role: text2("role").notNull(),
|
|
31455
|
+
content: text2("content").notNull(),
|
|
31456
|
+
occurredAt: timestamptz("occurred_at").notNull(),
|
|
31457
|
+
metadata: jsonb("metadata").$type().notNull().default({}),
|
|
31458
|
+
createdAt: timestamptz("created_at").notNull()
|
|
31459
|
+
}, (table) => [
|
|
31460
|
+
primaryKey({ columns: [table.workspaceId, table.seq] }),
|
|
31461
|
+
unique().on(table.workspaceId, table.messageId)
|
|
31462
|
+
]);
|
|
31297
31463
|
var workspaceStateHistory = pgTable("workspace_state_history", {
|
|
31298
31464
|
id: uuid3("id").primaryKey(),
|
|
31299
31465
|
workspaceId: uuid3("workspace_id").notNull().references(() => workspaces.id),
|
|
@@ -31406,7 +31572,7 @@ async function migrationStatus(sql2, schema) {
|
|
|
31406
31572
|
});
|
|
31407
31573
|
}
|
|
31408
31574
|
// ../db/src/store.ts
|
|
31409
|
-
import { randomUUID as
|
|
31575
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
31410
31576
|
// ../runtime-core/src/events.ts
|
|
31411
31577
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
31412
31578
|
function buildEventEnvelope(row, occurredAt) {
|
|
@@ -31420,6 +31586,7 @@ function buildEventEnvelope(row, occurredAt) {
|
|
|
31420
31586
|
state: row.state,
|
|
31421
31587
|
reason_code: row.reasonCode,
|
|
31422
31588
|
agent_state: row.agentState,
|
|
31589
|
+
provisioning_mode: row.provisioningMode,
|
|
31423
31590
|
change_cursor: row.changeSeq,
|
|
31424
31591
|
failure: row.state === "failed" && row.reasonCode ? {
|
|
31425
31592
|
reason_code: row.reasonCode,
|
|
@@ -31896,7 +32063,26 @@ class Scheduler {
|
|
|
31896
32063
|
return;
|
|
31897
32064
|
case "provisioning":
|
|
31898
32065
|
if (row.registrationExpiresAt && now >= row.registrationExpiresAt) {
|
|
31899
|
-
|
|
32066
|
+
if (row.provisioningMode === "warm" && row.launchAttempts < this.deps.limits.maxLaunchAttempts) {
|
|
32067
|
+
if (row.providerRef) {
|
|
32068
|
+
await this.deps.driver.stop(row.providerRef, 1).catch(() => {});
|
|
32069
|
+
await this.deps.driver.remove(row.providerRef).catch(() => {});
|
|
32070
|
+
}
|
|
32071
|
+
await this.deps.store.transition(row.id, {
|
|
32072
|
+
from: ["provisioning"],
|
|
32073
|
+
to: "queued",
|
|
32074
|
+
at: now,
|
|
32075
|
+
patch: {
|
|
32076
|
+
providerKind: null,
|
|
32077
|
+
providerRef: null,
|
|
32078
|
+
provisioningMode: null,
|
|
32079
|
+
registrationDigest: null,
|
|
32080
|
+
registrationExpiresAt: null
|
|
32081
|
+
}
|
|
32082
|
+
});
|
|
32083
|
+
} else {
|
|
32084
|
+
await this.fail(row, "registration_timeout", now);
|
|
32085
|
+
}
|
|
31900
32086
|
}
|
|
31901
32087
|
return;
|
|
31902
32088
|
case "connected":
|
|
@@ -32085,19 +32271,8 @@ class Scheduler {
|
|
|
32085
32271
|
const { store, driver, secrets } = this.deps;
|
|
32086
32272
|
const now = this.now();
|
|
32087
32273
|
const secret = secrets.generate();
|
|
32088
|
-
const
|
|
32089
|
-
|
|
32090
|
-
to: "provisioning",
|
|
32091
|
-
at: now,
|
|
32092
|
-
patch: {
|
|
32093
|
-
registrationDigest: secrets.digest(secret),
|
|
32094
|
-
registrationExpiresAt: new Date(now.getTime() + this.timeoutMs(row, "start")),
|
|
32095
|
-
launchAttempts: row.launchAttempts + 1
|
|
32096
|
-
}
|
|
32097
|
-
});
|
|
32098
|
-
if (!claimed) {
|
|
32099
|
-
return false;
|
|
32100
|
-
}
|
|
32274
|
+
const registrationDigest = secrets.digest(secret);
|
|
32275
|
+
const registrationExpiresAt = new Date(now.getTime() + this.timeoutMs(row, "start"));
|
|
32101
32276
|
const input = {
|
|
32102
32277
|
workspace_id: row.id,
|
|
32103
32278
|
server_url: this.deps.workspaceServerUrl,
|
|
@@ -32115,6 +32290,26 @@ class Scheduler {
|
|
|
32115
32290
|
} : {},
|
|
32116
32291
|
...row.launchInput ? { launch_input: row.launchInput } : {}
|
|
32117
32292
|
};
|
|
32293
|
+
if (this.deps.warmPool) {
|
|
32294
|
+
const hit = await this.deps.warmPool.tryLease(row, input, registrationDigest, registrationExpiresAt);
|
|
32295
|
+
if (hit)
|
|
32296
|
+
return true;
|
|
32297
|
+
if (this.deps.warmPool.missDecision(row) === "wait")
|
|
32298
|
+
return false;
|
|
32299
|
+
}
|
|
32300
|
+
const claimed = await store.transition(row.id, {
|
|
32301
|
+
from: ["queued"],
|
|
32302
|
+
to: "provisioning",
|
|
32303
|
+
at: now,
|
|
32304
|
+
patch: {
|
|
32305
|
+
provisioningMode: "cold",
|
|
32306
|
+
registrationDigest,
|
|
32307
|
+
registrationExpiresAt,
|
|
32308
|
+
launchAttempts: row.launchAttempts + 1
|
|
32309
|
+
}
|
|
32310
|
+
});
|
|
32311
|
+
if (!claimed)
|
|
32312
|
+
return false;
|
|
32118
32313
|
try {
|
|
32119
32314
|
const mounts = await this.prepareStorage(claimed);
|
|
32120
32315
|
if (!this.deps.secretResolver && JSON.stringify(claimed.templateSnapshot.spec).includes('"secretRef:')) {
|
|
@@ -32242,9 +32437,275 @@ class Scheduler {
|
|
|
32242
32437
|
await this.deps.store.updateOperation(operation.id, { state, reasonCode, completedAt: at, attemptCount: operation.attemptCount + 1 }, at);
|
|
32243
32438
|
}
|
|
32244
32439
|
}
|
|
32440
|
+
// ../runtime-core/src/warm-pool.ts
|
|
32441
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
32442
|
+
function warmPoolFingerprint(template2, driverKind) {
|
|
32443
|
+
return digestOf({ template_digest: template2.digest, driver: driverKind });
|
|
32444
|
+
}
|
|
32445
|
+
function validateWarmPoolTemplate(template2) {
|
|
32446
|
+
if (template2.spec.persistence.mounts.length > 0) {
|
|
32447
|
+
throw new Error(`warm pool ${template2.name}@${template2.version} is ineligible: persistent mounts are not supported`);
|
|
32448
|
+
}
|
|
32449
|
+
if (JSON.stringify(template2.spec).includes('"secretRef:')) {
|
|
32450
|
+
throw new Error(`warm pool ${template2.name}@${template2.version} is ineligible: resolved secret mounts are not supported`);
|
|
32451
|
+
}
|
|
32452
|
+
}
|
|
32453
|
+
async function resolveWarmPools(store, entries, driverKind, globalLimit) {
|
|
32454
|
+
if (entries.reduce((sum3, entry) => sum3 + entry.minReady, 0) > globalLimit) {
|
|
32455
|
+
throw new Error("warm pool desired capacity exceeds POCKETCODER_MAX_ACTIVE_WORKSPACES");
|
|
32456
|
+
}
|
|
32457
|
+
const seen = new Set;
|
|
32458
|
+
const resolved = [];
|
|
32459
|
+
for (const entry of entries) {
|
|
32460
|
+
const template2 = await store.getTemplate(entry.template, entry.version);
|
|
32461
|
+
if (!template2 || template2.status === "retired") {
|
|
32462
|
+
throw new Error(`warm pool template not found: ${entry.template}${entry.version ? `@${entry.version}` : ""}`);
|
|
32463
|
+
}
|
|
32464
|
+
validateWarmPoolTemplate(template2);
|
|
32465
|
+
if (seen.has(template2.digest))
|
|
32466
|
+
throw new Error(`duplicate warm pool template digest: ${template2.digest}`);
|
|
32467
|
+
seen.add(template2.digest);
|
|
32468
|
+
resolved.push({
|
|
32469
|
+
...entry,
|
|
32470
|
+
templateRow: template2,
|
|
32471
|
+
eligibilityFingerprint: warmPoolFingerprint(template2, driverKind)
|
|
32472
|
+
});
|
|
32473
|
+
}
|
|
32474
|
+
return resolved;
|
|
32475
|
+
}
|
|
32476
|
+
|
|
32477
|
+
class WarmPoolManager {
|
|
32478
|
+
deps;
|
|
32479
|
+
metrics = {
|
|
32480
|
+
warmHits: 0,
|
|
32481
|
+
misses: 0,
|
|
32482
|
+
leaseFailures: 0,
|
|
32483
|
+
replenishFailures: 0,
|
|
32484
|
+
staleCleanups: 0,
|
|
32485
|
+
leaseLatencyMsTotal: 0
|
|
32486
|
+
};
|
|
32487
|
+
constructor(deps) {
|
|
32488
|
+
this.deps = deps;
|
|
32489
|
+
}
|
|
32490
|
+
now() {
|
|
32491
|
+
return this.deps.now?.() ?? new Date;
|
|
32492
|
+
}
|
|
32493
|
+
poolFor(row) {
|
|
32494
|
+
return this.deps.pools.find((pool) => pool.templateRow.digest === row.templateDigest);
|
|
32495
|
+
}
|
|
32496
|
+
missDecision(row) {
|
|
32497
|
+
const pool = this.poolFor(row);
|
|
32498
|
+
if (!pool || pool.missPolicy === "cold")
|
|
32499
|
+
return "cold";
|
|
32500
|
+
return this.now().getTime() - row.createdAt.getTime() < pool.waitTimeoutMs ? "wait" : "cold";
|
|
32501
|
+
}
|
|
32502
|
+
async tryLease(row, input, registrationDigest, registrationExpiresAt) {
|
|
32503
|
+
const pool = this.poolFor(row);
|
|
32504
|
+
if (!pool)
|
|
32505
|
+
return false;
|
|
32506
|
+
const started = this.now();
|
|
32507
|
+
const claimed = await this.deps.store.claimWarmPoolRuntime({
|
|
32508
|
+
workspaceId: row.id,
|
|
32509
|
+
templateDigest: row.templateDigest,
|
|
32510
|
+
driverKind: this.deps.driver.kind,
|
|
32511
|
+
eligibilityFingerprint: pool.eligibilityFingerprint,
|
|
32512
|
+
registrationDigest,
|
|
32513
|
+
registrationExpiresAt,
|
|
32514
|
+
at: started
|
|
32515
|
+
});
|
|
32516
|
+
if (!claimed) {
|
|
32517
|
+
this.metrics.misses += 1;
|
|
32518
|
+
return false;
|
|
32519
|
+
}
|
|
32520
|
+
await this.deps.store.updateWarmPoolRuntime(claimed.runtime.id, { state: "leased" }, this.now());
|
|
32521
|
+
if (!this.deps.connections.assign(claimed.runtime.id, input)) {
|
|
32522
|
+
this.metrics.leaseFailures += 1;
|
|
32523
|
+
await this.destroy(claimed.runtime, "assignment_connection_lost");
|
|
32524
|
+
await this.deps.store.transition(row.id, {
|
|
32525
|
+
from: ["provisioning"],
|
|
32526
|
+
to: "queued",
|
|
32527
|
+
at: this.now(),
|
|
32528
|
+
patch: {
|
|
32529
|
+
providerKind: null,
|
|
32530
|
+
providerRef: null,
|
|
32531
|
+
provisioningMode: null,
|
|
32532
|
+
registrationDigest: null,
|
|
32533
|
+
registrationExpiresAt: null
|
|
32534
|
+
}
|
|
32535
|
+
});
|
|
32536
|
+
return false;
|
|
32537
|
+
}
|
|
32538
|
+
this.metrics.warmHits += 1;
|
|
32539
|
+
this.metrics.leaseLatencyMsTotal += this.now().getTime() - started.getTime();
|
|
32540
|
+
return true;
|
|
32541
|
+
}
|
|
32542
|
+
async markReady(runtimeId) {
|
|
32543
|
+
let row = await this.deps.store.getWarmPoolRuntime(runtimeId);
|
|
32544
|
+
for (let attempt = 0;row?.state === "provisioning" && !row.providerRef && attempt < 50; attempt += 1) {
|
|
32545
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
32546
|
+
row = await this.deps.store.getWarmPoolRuntime(runtimeId);
|
|
32547
|
+
}
|
|
32548
|
+
if (row?.state !== "provisioning" || !row.providerRef)
|
|
32549
|
+
return false;
|
|
32550
|
+
const now = this.now();
|
|
32551
|
+
await this.deps.store.updateWarmPoolRuntime(runtimeId, {
|
|
32552
|
+
state: "ready",
|
|
32553
|
+
readyAt: now,
|
|
32554
|
+
enrollmentDigest: null,
|
|
32555
|
+
enrollmentExpiresAt: null
|
|
32556
|
+
}, now);
|
|
32557
|
+
this.deps.driver.cleanupWarmInput?.(runtimeId).catch(() => {});
|
|
32558
|
+
return true;
|
|
32559
|
+
}
|
|
32560
|
+
async disconnected(runtimeId) {
|
|
32561
|
+
const row = await this.deps.store.getWarmPoolRuntime(runtimeId);
|
|
32562
|
+
if (row && ["provisioning", "ready", "leasing"].includes(row.state)) {
|
|
32563
|
+
await this.destroy(row, "pool_agent_disconnected");
|
|
32564
|
+
}
|
|
32565
|
+
}
|
|
32566
|
+
async destroy(row, reason) {
|
|
32567
|
+
await this.deps.store.updateWarmPoolRuntime(row.id, { state: "draining", failureCode: reason }, this.now());
|
|
32568
|
+
this.deps.connections.close(row.id);
|
|
32569
|
+
if (row.providerRef) {
|
|
32570
|
+
await this.deps.driver.stop(row.providerRef, 1).catch(() => {});
|
|
32571
|
+
await this.deps.driver.remove(row.providerRef).catch(() => {});
|
|
32572
|
+
}
|
|
32573
|
+
await this.deps.store.updateWarmPoolRuntime(row.id, { state: "failed", failureCode: reason }, this.now());
|
|
32574
|
+
}
|
|
32575
|
+
async create(pool) {
|
|
32576
|
+
const now = this.now();
|
|
32577
|
+
const id = randomUUID4();
|
|
32578
|
+
const secret = this.deps.secrets.generate();
|
|
32579
|
+
const row = {
|
|
32580
|
+
id,
|
|
32581
|
+
templateId: pool.templateRow.id,
|
|
32582
|
+
templateName: pool.templateRow.name,
|
|
32583
|
+
templateVersion: pool.templateRow.version,
|
|
32584
|
+
templateDigest: pool.templateRow.digest,
|
|
32585
|
+
driverKind: this.deps.driver.kind,
|
|
32586
|
+
eligibilityFingerprint: pool.eligibilityFingerprint,
|
|
32587
|
+
state: "provisioning",
|
|
32588
|
+
providerRef: null,
|
|
32589
|
+
enrollmentDigest: this.deps.secrets.digest(secret),
|
|
32590
|
+
enrollmentExpiresAt: new Date(now.getTime() + parseDurationMs(pool.templateRow.spec.timeouts.start)),
|
|
32591
|
+
workspaceId: null,
|
|
32592
|
+
createdAt: now,
|
|
32593
|
+
updatedAt: now,
|
|
32594
|
+
readyAt: null,
|
|
32595
|
+
leasedAt: null,
|
|
32596
|
+
failureCode: null
|
|
32597
|
+
};
|
|
32598
|
+
await this.deps.store.insertWarmPoolRuntime(row);
|
|
32599
|
+
const input = {
|
|
32600
|
+
pool_runtime_id: id,
|
|
32601
|
+
server_url: this.deps.workspaceServerUrl,
|
|
32602
|
+
enrollment_secret: secret,
|
|
32603
|
+
template_digest: pool.templateRow.digest,
|
|
32604
|
+
template_name: pool.templateRow.name,
|
|
32605
|
+
template_version: pool.templateRow.version
|
|
32606
|
+
};
|
|
32607
|
+
try {
|
|
32608
|
+
const template2 = {
|
|
32609
|
+
name: pool.templateRow.name,
|
|
32610
|
+
version: pool.templateRow.version,
|
|
32611
|
+
digest: pool.templateRow.digest,
|
|
32612
|
+
spec: pool.templateRow.spec
|
|
32613
|
+
};
|
|
32614
|
+
const ref = await this.deps.driver.createWarm({ runtimeId: id, template: template2, input });
|
|
32615
|
+
await this.deps.store.updateWarmPoolRuntime(id, { providerRef: ref }, this.now());
|
|
32616
|
+
} catch (error51) {
|
|
32617
|
+
this.metrics.replenishFailures += 1;
|
|
32618
|
+
await this.deps.store.updateWarmPoolRuntime(id, { state: "failed", failureCode: "provider_create_failed" }, this.now());
|
|
32619
|
+
this.deps.onError?.(`warm-pool.create.${id}`, error51);
|
|
32620
|
+
}
|
|
32621
|
+
}
|
|
32622
|
+
async cleanupReason(row, pool, now) {
|
|
32623
|
+
if (!pool)
|
|
32624
|
+
return "pool_retired";
|
|
32625
|
+
if (["provisioning", "ready"].includes(row.state) && row.createdAt.getTime() + pool.maxWarmAgeMs <= now.getTime())
|
|
32626
|
+
return "max_warm_age";
|
|
32627
|
+
if (row.state === "provisioning" && row.enrollmentExpiresAt !== null && row.enrollmentExpiresAt <= now) {
|
|
32628
|
+
return "enrollment_timeout";
|
|
32629
|
+
}
|
|
32630
|
+
if (!row.workspaceId)
|
|
32631
|
+
return null;
|
|
32632
|
+
return (await this.deps.store.getWorkspace(row.workspaceId))?.terminalAt ? "workspace_ended" : null;
|
|
32633
|
+
}
|
|
32634
|
+
async reconcileRow(row, pool, now) {
|
|
32635
|
+
const reason = await this.cleanupReason(row, pool, now);
|
|
32636
|
+
if (reason) {
|
|
32637
|
+
if (row.state !== "failed") {
|
|
32638
|
+
this.metrics.staleCleanups += 1;
|
|
32639
|
+
await this.destroy(row, reason);
|
|
32640
|
+
}
|
|
32641
|
+
return;
|
|
32642
|
+
}
|
|
32643
|
+
if (!row.providerRef || ["failed", "draining"].includes(row.state))
|
|
32644
|
+
return;
|
|
32645
|
+
const state = await this.deps.driver.inspect(row.providerRef);
|
|
32646
|
+
if (!state.exists || !state.running)
|
|
32647
|
+
await this.destroy(row, "provider_lost");
|
|
32648
|
+
}
|
|
32649
|
+
async reconcile() {
|
|
32650
|
+
const now = this.now();
|
|
32651
|
+
const configured = new Map(this.deps.pools.map((pool) => [pool.templateRow.digest, pool]));
|
|
32652
|
+
const rows = await this.deps.store.listWarmPoolRuntimes();
|
|
32653
|
+
const known = new Map(rows.map((row) => [row.id, row]));
|
|
32654
|
+
for (const provider of await this.deps.driver.listWarm()) {
|
|
32655
|
+
const row = known.get(provider.runtimeId);
|
|
32656
|
+
if (!row || row.templateDigest !== provider.templateDigest || row.driverKind !== this.deps.driver.kind) {
|
|
32657
|
+
this.metrics.staleCleanups += 1;
|
|
32658
|
+
await this.deps.driver.stop(provider.ref, 1).catch(() => {});
|
|
32659
|
+
await this.deps.driver.remove(provider.ref).catch(() => {});
|
|
32660
|
+
continue;
|
|
32661
|
+
}
|
|
32662
|
+
if (!row.providerRef && row.state === "provisioning") {
|
|
32663
|
+
await this.deps.store.updateWarmPoolRuntime(row.id, { providerRef: provider.ref }, now);
|
|
32664
|
+
row.providerRef = provider.ref;
|
|
32665
|
+
}
|
|
32666
|
+
}
|
|
32667
|
+
for (const row of rows) {
|
|
32668
|
+
await this.reconcileRow(row, configured.get(row.templateDigest), now);
|
|
32669
|
+
}
|
|
32670
|
+
const fresh = await this.deps.store.listWarmPoolRuntimes();
|
|
32671
|
+
for (const pool of this.deps.pools) {
|
|
32672
|
+
const available = fresh.filter((row) => row.templateDigest === pool.templateRow.digest && ["provisioning", "ready"].includes(row.state)).length;
|
|
32673
|
+
for (let i = available;i < pool.minReady; i += 1)
|
|
32674
|
+
await this.create(pool);
|
|
32675
|
+
}
|
|
32676
|
+
}
|
|
32677
|
+
async inventory() {
|
|
32678
|
+
const rows = await this.deps.store.listWarmPoolRuntimes();
|
|
32679
|
+
const now = this.now();
|
|
32680
|
+
return {
|
|
32681
|
+
items: this.deps.pools.map((pool) => {
|
|
32682
|
+
const matching = rows.filter((row) => row.templateDigest === pool.templateRow.digest);
|
|
32683
|
+
const counts = {};
|
|
32684
|
+
for (const row of matching)
|
|
32685
|
+
counts[row.state] = (counts[row.state] ?? 0) + 1;
|
|
32686
|
+
const ready = matching.filter((row) => row.state === "ready" && row.readyAt).map((row) => row.readyAt);
|
|
32687
|
+
return {
|
|
32688
|
+
template: pool.templateRow.name,
|
|
32689
|
+
version: pool.templateRow.version,
|
|
32690
|
+
template_digest: pool.templateRow.digest,
|
|
32691
|
+
driver: this.deps.driver.kind,
|
|
32692
|
+
desired: pool.minReady,
|
|
32693
|
+
counts,
|
|
32694
|
+
oldest_ready_age_ms: ready.length ? now.getTime() - Math.min(...ready.map((date9) => date9.getTime())) : null
|
|
32695
|
+
};
|
|
32696
|
+
}),
|
|
32697
|
+
metrics: {
|
|
32698
|
+
...this.metrics,
|
|
32699
|
+
average_lease_latency_ms: this.metrics.warmHits ? this.metrics.leaseLatencyMsTotal / this.metrics.warmHits : 0
|
|
32700
|
+
}
|
|
32701
|
+
};
|
|
32702
|
+
}
|
|
32703
|
+
}
|
|
32245
32704
|
// ../db/src/store.ts
|
|
32246
32705
|
var {SQL: SQL6 } = globalThis.Bun;
|
|
32247
32706
|
var MAX_LOG_BYTES = 10 * 1024 * 1024;
|
|
32707
|
+
var MAX_CONVERSATION_BYTES = 50 * 1024 * 1024;
|
|
32708
|
+
var MAX_CONVERSATION_MESSAGES = 1e5;
|
|
32248
32709
|
var CLAIM_LEASE_MS = 60000;
|
|
32249
32710
|
function pgTextArray(items) {
|
|
32250
32711
|
return `{${items.map((i) => `"${i.replace(/(["\\])/g, "\\$1")}"`).join(",")}}`;
|
|
@@ -32340,7 +32801,7 @@ class PostgresStore {
|
|
|
32340
32801
|
(id, name, version, digest, description, spec, status, created_at)
|
|
32341
32802
|
VALUES ($1, $2, $3, $4, $5, $6::jsonb, 'active', now())
|
|
32342
32803
|
RETURNING *`, [
|
|
32343
|
-
|
|
32804
|
+
randomUUID5(),
|
|
32344
32805
|
input.name,
|
|
32345
32806
|
input.version,
|
|
32346
32807
|
input.digest,
|
|
@@ -32364,6 +32825,127 @@ class PostgresStore {
|
|
|
32364
32825
|
SET status = $3, retired_at = CASE WHEN $3 = 'retired' THEN now() ELSE NULL END
|
|
32365
32826
|
WHERE name = $1 AND version = $2`, [name2, version2, status]);
|
|
32366
32827
|
}
|
|
32828
|
+
warmPoolRuntimeFromRow(r) {
|
|
32829
|
+
return {
|
|
32830
|
+
id: String(r.id),
|
|
32831
|
+
templateId: String(r.template_id),
|
|
32832
|
+
templateName: String(r.template_name),
|
|
32833
|
+
templateVersion: String(r.template_version),
|
|
32834
|
+
templateDigest: String(r.template_digest),
|
|
32835
|
+
driverKind: String(r.driver_kind),
|
|
32836
|
+
eligibilityFingerprint: String(r.eligibility_fingerprint),
|
|
32837
|
+
state: r.state,
|
|
32838
|
+
providerRef: r.provider_ref == null ? null : asJson(r.provider_ref),
|
|
32839
|
+
enrollmentDigest: asBytes(r.enrollment_digest),
|
|
32840
|
+
enrollmentExpiresAt: asDateOrNull(r.enrollment_expires_at),
|
|
32841
|
+
workspaceId: r.workspace_id ?? null,
|
|
32842
|
+
createdAt: asDate(r.created_at),
|
|
32843
|
+
updatedAt: asDate(r.updated_at),
|
|
32844
|
+
readyAt: asDateOrNull(r.ready_at),
|
|
32845
|
+
leasedAt: asDateOrNull(r.leased_at),
|
|
32846
|
+
failureCode: r.failure_code ?? null
|
|
32847
|
+
};
|
|
32848
|
+
}
|
|
32849
|
+
async insertWarmPoolRuntime(row) {
|
|
32850
|
+
const rows = await this.sql.unsafe(`INSERT INTO ${this.t("warm_pool_runtimes")}
|
|
32851
|
+
(id, template_id, template_name, template_version, template_digest, driver_kind,
|
|
32852
|
+
eligibility_fingerprint, state, provider_ref, enrollment_digest,
|
|
32853
|
+
enrollment_expires_at, workspace_id, created_at, updated_at, ready_at,
|
|
32854
|
+
leased_at, failure_code)
|
|
32855
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10,$11,$12,$13,$14,$15,$16,$17)
|
|
32856
|
+
ON CONFLICT (id) DO NOTHING RETURNING *`, [
|
|
32857
|
+
row.id,
|
|
32858
|
+
row.templateId,
|
|
32859
|
+
row.templateName,
|
|
32860
|
+
row.templateVersion,
|
|
32861
|
+
row.templateDigest,
|
|
32862
|
+
row.driverKind,
|
|
32863
|
+
row.eligibilityFingerprint,
|
|
32864
|
+
row.state,
|
|
32865
|
+
row.providerRef == null ? null : JSON.stringify(row.providerRef),
|
|
32866
|
+
row.enrollmentDigest,
|
|
32867
|
+
row.enrollmentExpiresAt,
|
|
32868
|
+
row.workspaceId,
|
|
32869
|
+
row.createdAt,
|
|
32870
|
+
row.updatedAt,
|
|
32871
|
+
row.readyAt,
|
|
32872
|
+
row.leasedAt,
|
|
32873
|
+
row.failureCode
|
|
32874
|
+
]);
|
|
32875
|
+
return rows[0] ? this.warmPoolRuntimeFromRow(rows[0]) : await this.getWarmPoolRuntime(row.id);
|
|
32876
|
+
}
|
|
32877
|
+
async getWarmPoolRuntime(id) {
|
|
32878
|
+
const rows = await this.sql.unsafe(`SELECT * FROM ${this.t("warm_pool_runtimes")} WHERE id = $1`, [id]);
|
|
32879
|
+
return rows[0] ? this.warmPoolRuntimeFromRow(rows[0]) : null;
|
|
32880
|
+
}
|
|
32881
|
+
async listWarmPoolRuntimes() {
|
|
32882
|
+
const rows = await this.sql.unsafe(`SELECT * FROM ${this.t("warm_pool_runtimes")} ORDER BY created_at`);
|
|
32883
|
+
return rows.map((row) => this.warmPoolRuntimeFromRow(row));
|
|
32884
|
+
}
|
|
32885
|
+
async updateWarmPoolRuntime(id, patch, at) {
|
|
32886
|
+
const columns = {
|
|
32887
|
+
state: "state",
|
|
32888
|
+
providerRef: "provider_ref",
|
|
32889
|
+
enrollmentDigest: "enrollment_digest",
|
|
32890
|
+
enrollmentExpiresAt: "enrollment_expires_at",
|
|
32891
|
+
workspaceId: "workspace_id",
|
|
32892
|
+
readyAt: "ready_at",
|
|
32893
|
+
leasedAt: "leased_at",
|
|
32894
|
+
failureCode: "failure_code"
|
|
32895
|
+
};
|
|
32896
|
+
const params = [id, at];
|
|
32897
|
+
const sets = ["updated_at = $2"];
|
|
32898
|
+
for (const [key, column] of Object.entries(columns)) {
|
|
32899
|
+
if (!(key in patch))
|
|
32900
|
+
continue;
|
|
32901
|
+
const value = patch[key] ?? null;
|
|
32902
|
+
params.push(key === "providerRef" && value !== null ? JSON.stringify(value) : value);
|
|
32903
|
+
sets.push(`${column} = $${params.length}${key === "providerRef" ? "::jsonb" : ""}`);
|
|
32904
|
+
}
|
|
32905
|
+
await this.sql.unsafe(`UPDATE ${this.t("warm_pool_runtimes")} SET ${sets.join(", ")} WHERE id = $1`, params);
|
|
32906
|
+
}
|
|
32907
|
+
async claimWarmPoolRuntime(claim) {
|
|
32908
|
+
const result = await this.sql.begin(async (tx) => {
|
|
32909
|
+
const workspaceRows = await tx.unsafe(`SELECT * FROM ${this.t("workspaces")} WHERE id = $1 FOR UPDATE`, [claim.workspaceId]);
|
|
32910
|
+
const current = workspaceRows[0] ? this.workspaceFromRow(workspaceRows[0]) : null;
|
|
32911
|
+
if (current?.state !== "queued")
|
|
32912
|
+
return null;
|
|
32913
|
+
const runtimeRows = await tx.unsafe(`SELECT * FROM ${this.t("warm_pool_runtimes")}
|
|
32914
|
+
WHERE template_digest = $1 AND driver_kind = $2 AND eligibility_fingerprint = $3
|
|
32915
|
+
AND state = 'ready' AND provider_ref IS NOT NULL
|
|
32916
|
+
ORDER BY ready_at FOR UPDATE SKIP LOCKED LIMIT 1`, [claim.templateDigest, claim.driverKind, claim.eligibilityFingerprint]);
|
|
32917
|
+
if (!runtimeRows[0])
|
|
32918
|
+
return null;
|
|
32919
|
+
const runtime = this.warmPoolRuntimeFromRow(runtimeRows[0]);
|
|
32920
|
+
const leasedRows = await tx.unsafe(`UPDATE ${this.t("warm_pool_runtimes")}
|
|
32921
|
+
SET state='leasing', workspace_id=$2, leased_at=$3, updated_at=$3
|
|
32922
|
+
WHERE id=$1 AND state='ready' RETURNING *`, [runtime.id, current.id, claim.at]);
|
|
32923
|
+
if (!leasedRows[0])
|
|
32924
|
+
return null;
|
|
32925
|
+
const updatedRows = await tx.unsafe(`UPDATE ${this.t("workspaces")}
|
|
32926
|
+
SET state='provisioning', provisioning_mode='warm', provider_kind=$2,
|
|
32927
|
+
provider_ref=$3::jsonb, registration_digest=$4,
|
|
32928
|
+
registration_expires_at=$5, launch_attempts=launch_attempts+1,
|
|
32929
|
+
updated_at=$6, change_seq=change_seq+1
|
|
32930
|
+
WHERE id=$1 AND state='queued' RETURNING *`, [
|
|
32931
|
+
current.id,
|
|
32932
|
+
runtime.driverKind,
|
|
32933
|
+
JSON.stringify(runtime.providerRef),
|
|
32934
|
+
claim.registrationDigest,
|
|
32935
|
+
claim.registrationExpiresAt,
|
|
32936
|
+
claim.at
|
|
32937
|
+
]);
|
|
32938
|
+
if (!updatedRows[0])
|
|
32939
|
+
throw new Error("warm_pool.claim_workspace_race");
|
|
32940
|
+
const workspace2 = this.workspaceFromRow(updatedRows[0]);
|
|
32941
|
+
await this.appendHistoryTx(tx, workspace2, "queued", "provisioning", null, claim.at);
|
|
32942
|
+
await this.appendEventTx(tx, workspace2, claim.at);
|
|
32943
|
+
return { runtime: this.warmPoolRuntimeFromRow(leasedRows[0]), workspace: workspace2 };
|
|
32944
|
+
});
|
|
32945
|
+
if (result)
|
|
32946
|
+
this.notifyWorkspaceChange(claim.workspaceId);
|
|
32947
|
+
return result;
|
|
32948
|
+
}
|
|
32367
32949
|
principalFromRow(r) {
|
|
32368
32950
|
return {
|
|
32369
32951
|
id: String(r.id),
|
|
@@ -32376,7 +32958,7 @@ class PostgresStore {
|
|
|
32376
32958
|
}
|
|
32377
32959
|
async createPrincipal(name2, scopes2, templateNames) {
|
|
32378
32960
|
const rows = await this.sql.unsafe(`INSERT INTO ${this.t("principals")} (id, name, scopes, template_names, created_at)
|
|
32379
|
-
VALUES ($1, $2, $3::text[], $4::text[], now()) RETURNING *`, [
|
|
32961
|
+
VALUES ($1, $2, $3::text[], $4::text[], now()) RETURNING *`, [randomUUID5(), name2, pgTextArray(scopes2), pgTextArray(templateNames)]);
|
|
32380
32962
|
return this.principalFromRow(rows[0]);
|
|
32381
32963
|
}
|
|
32382
32964
|
async getPrincipalByName(name2) {
|
|
@@ -32477,6 +33059,7 @@ class PostgresStore {
|
|
|
32477
33059
|
launchInput: r.launch_input == null ? null : asJson(r.launch_input),
|
|
32478
33060
|
providerKind: r.provider_kind ?? null,
|
|
32479
33061
|
providerRef: r.provider_ref == null ? null : asJson(r.provider_ref),
|
|
33062
|
+
provisioningMode: r.provisioning_mode ?? null,
|
|
32480
33063
|
registrationDigest: asBytes(r.registration_digest),
|
|
32481
33064
|
registrationExpiresAt: asDateOrNull(r.registration_expires_at),
|
|
32482
33065
|
reconnectDigest: asBytes(r.reconnect_digest),
|
|
@@ -32586,13 +33169,26 @@ class PostgresStore {
|
|
|
32586
33169
|
params.push(filter.template);
|
|
32587
33170
|
clauses.push(`template_name = $${params.length}`);
|
|
32588
33171
|
}
|
|
33172
|
+
if (filter.metadata) {
|
|
33173
|
+
params.push(JSON.stringify(filter.metadata));
|
|
33174
|
+
clauses.push(`metadata @> $${params.length}::jsonb`);
|
|
33175
|
+
}
|
|
33176
|
+
if (filter.createdAfter) {
|
|
33177
|
+
params.push(filter.createdAfter);
|
|
33178
|
+
clauses.push(`created_at >= $${params.length}`);
|
|
33179
|
+
}
|
|
33180
|
+
if (filter.createdBefore) {
|
|
33181
|
+
params.push(filter.createdBefore);
|
|
33182
|
+
clauses.push(`created_at < $${params.length}`);
|
|
33183
|
+
}
|
|
32589
33184
|
if (filter.cursor) {
|
|
32590
33185
|
params.push(filter.cursor);
|
|
32591
|
-
clauses.push(`created_at < (SELECT created_at FROM ${this.t("workspaces")}
|
|
33186
|
+
clauses.push(`(created_at, id) < (SELECT created_at, id FROM ${this.t("workspaces")}
|
|
33187
|
+
WHERE id = $${params.length} AND principal_id = $1)`);
|
|
32592
33188
|
}
|
|
32593
33189
|
params.push(filter.limit);
|
|
32594
33190
|
const rows = await this.sql.unsafe(`SELECT * FROM ${this.t("workspaces")} WHERE ${clauses.join(" AND ")}
|
|
32595
|
-
|
|
33191
|
+
ORDER BY created_at DESC, id DESC LIMIT $${params.length}`, params);
|
|
32596
33192
|
return rows.map((r) => this.workspaceFromRow(r));
|
|
32597
33193
|
}
|
|
32598
33194
|
async listQueued(limit) {
|
|
@@ -32627,6 +33223,7 @@ class PostgresStore {
|
|
|
32627
33223
|
launchInput: "launch_input",
|
|
32628
33224
|
providerKind: "provider_kind",
|
|
32629
33225
|
providerRef: "provider_ref",
|
|
33226
|
+
provisioningMode: "provisioning_mode",
|
|
32630
33227
|
registrationDigest: "registration_digest",
|
|
32631
33228
|
registrationExpiresAt: "registration_expires_at",
|
|
32632
33229
|
reconnectDigest: "reconnect_digest",
|
|
@@ -32642,6 +33239,7 @@ class PostgresStore {
|
|
|
32642
33239
|
failureLogTailTruncated: "failure_log_tail_truncated",
|
|
32643
33240
|
failureLastLogSeq: "failure_last_log_seq",
|
|
32644
33241
|
resolvedSource: "resolved_source",
|
|
33242
|
+
persistenceCapability: "persistence_capability",
|
|
32645
33243
|
latestCheckpointId: "latest_checkpoint_id",
|
|
32646
33244
|
outputs: "outputs"
|
|
32647
33245
|
};
|
|
@@ -32661,6 +33259,7 @@ class PostgresStore {
|
|
|
32661
33259
|
"failureLogTailTruncated",
|
|
32662
33260
|
"failureLastLogSeq",
|
|
32663
33261
|
"resolvedSource",
|
|
33262
|
+
"persistenceCapability",
|
|
32664
33263
|
"latestCheckpointId",
|
|
32665
33264
|
"outputs"
|
|
32666
33265
|
]);
|
|
@@ -32757,6 +33356,15 @@ class PostgresStore {
|
|
|
32757
33356
|
const workspace3 = this.workspaceFromRow(updated[0]);
|
|
32758
33357
|
await this.appendHistoryTx(tx, workspace3, current.state, req.to, workspace3.reasonCode, req.at);
|
|
32759
33358
|
await this.appendEventTx(tx, workspace3, req.at);
|
|
33359
|
+
if (isTerminal(req.to)) {
|
|
33360
|
+
const expiresAt = new Date(req.at.getTime() + parseDurationMs(workspace3.templateSnapshot.spec.persistence.conversationRetention));
|
|
33361
|
+
await tx.unsafe(`INSERT INTO ${this.t("workspace_conversations")}
|
|
33362
|
+
(workspace_id, status, expires_at, deleted_at, updated_at)
|
|
33363
|
+
VALUES ($1, 'retained', $2, NULL, $3)
|
|
33364
|
+
ON CONFLICT (workspace_id) DO UPDATE
|
|
33365
|
+
SET expires_at = EXCLUDED.expires_at, updated_at = EXCLUDED.updated_at
|
|
33366
|
+
WHERE ${this.t("workspace_conversations")}.status <> 'deleted'`, [workspace3.id, expiresAt, req.at]);
|
|
33367
|
+
}
|
|
32760
33368
|
return workspace3;
|
|
32761
33369
|
});
|
|
32762
33370
|
if (workspace2)
|
|
@@ -33124,7 +33732,7 @@ class PostgresStore {
|
|
|
33124
33732
|
async appendHistoryTx(tx, row, from, to, reason, at) {
|
|
33125
33733
|
await tx.unsafe(`INSERT INTO ${this.t("workspace_state_history")}
|
|
33126
33734
|
(id, workspace_id, from_state, to_state, reason_code, occurred_at)
|
|
33127
|
-
VALUES ($1, $2, $3, $4, $5, $6)`, [
|
|
33735
|
+
VALUES ($1, $2, $3, $4, $5, $6)`, [randomUUID5(), row.id, from, to, reason, at]);
|
|
33128
33736
|
}
|
|
33129
33737
|
async appendEventTx(tx, row, at) {
|
|
33130
33738
|
const payload = buildEventEnvelope(row, at);
|
|
@@ -33190,6 +33798,104 @@ class PostgresStore {
|
|
|
33190
33798
|
lastSeq
|
|
33191
33799
|
};
|
|
33192
33800
|
}
|
|
33801
|
+
conversationMessageFromRow(row) {
|
|
33802
|
+
return {
|
|
33803
|
+
workspaceId: String(row.workspace_id),
|
|
33804
|
+
seq: Number(row.seq),
|
|
33805
|
+
messageId: String(row.message_id),
|
|
33806
|
+
role: row.role,
|
|
33807
|
+
content: String(row.content),
|
|
33808
|
+
occurredAt: asDate(row.occurred_at),
|
|
33809
|
+
metadata: asJson(row.metadata),
|
|
33810
|
+
createdAt: asDate(row.created_at)
|
|
33811
|
+
};
|
|
33812
|
+
}
|
|
33813
|
+
async appendConversationMessage(input) {
|
|
33814
|
+
return await this.sql.begin(async (tx) => {
|
|
33815
|
+
await tx.unsafe("SELECT pg_advisory_xact_lock(hashtextextended($1, 7081))", [
|
|
33816
|
+
input.workspaceId
|
|
33817
|
+
]);
|
|
33818
|
+
const states = await tx.unsafe(`SELECT status FROM ${this.t("workspace_conversations")} WHERE workspace_id = $1 FOR UPDATE`, [input.workspaceId]);
|
|
33819
|
+
if (states[0]?.status === "deleted")
|
|
33820
|
+
throw new Error("conversation.deleted");
|
|
33821
|
+
const existing = await tx.unsafe(`SELECT * FROM ${this.t("workspace_conversation_messages")}
|
|
33822
|
+
WHERE workspace_id = $1 AND message_id = $2`, [input.workspaceId, input.messageId]);
|
|
33823
|
+
if (existing[0]) {
|
|
33824
|
+
return { message: this.conversationMessageFromRow(existing[0]), created: false };
|
|
33825
|
+
}
|
|
33826
|
+
await tx.unsafe(`INSERT INTO ${this.t("workspace_conversations")}
|
|
33827
|
+
(workspace_id, status, expires_at, deleted_at, updated_at)
|
|
33828
|
+
VALUES ($1, 'retained', NULL, NULL, $2)
|
|
33829
|
+
ON CONFLICT (workspace_id) DO NOTHING`, [input.workspaceId, input.createdAt]);
|
|
33830
|
+
const stats = await tx.unsafe(`SELECT COALESCE(MAX(seq), 0)::bigint AS max_seq,
|
|
33831
|
+
COUNT(*)::bigint AS message_count,
|
|
33832
|
+
COALESCE(SUM(octet_length(content) + octet_length(metadata::text)), 0)::bigint AS bytes
|
|
33833
|
+
FROM ${this.t("workspace_conversation_messages")} WHERE workspace_id = $1`, [input.workspaceId]);
|
|
33834
|
+
const inputBytes = Buffer.byteLength(input.content) + Buffer.byteLength(JSON.stringify(input.metadata));
|
|
33835
|
+
if (Number(stats[0]?.message_count ?? 0) >= MAX_CONVERSATION_MESSAGES || Number(stats[0]?.bytes ?? 0) + inputBytes > MAX_CONVERSATION_BYTES) {
|
|
33836
|
+
throw new Error("conversation.quota_exceeded");
|
|
33837
|
+
}
|
|
33838
|
+
const seq = Number(stats[0]?.max_seq ?? 0) + 1;
|
|
33839
|
+
const inserted = await tx.unsafe(`INSERT INTO ${this.t("workspace_conversation_messages")}
|
|
33840
|
+
(workspace_id, seq, message_id, role, content, occurred_at, metadata, created_at)
|
|
33841
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8) RETURNING *`, [
|
|
33842
|
+
input.workspaceId,
|
|
33843
|
+
seq,
|
|
33844
|
+
input.messageId,
|
|
33845
|
+
input.role,
|
|
33846
|
+
input.content,
|
|
33847
|
+
input.occurredAt,
|
|
33848
|
+
JSON.stringify(input.metadata),
|
|
33849
|
+
input.createdAt
|
|
33850
|
+
]);
|
|
33851
|
+
return { message: this.conversationMessageFromRow(inserted[0]), created: true };
|
|
33852
|
+
});
|
|
33853
|
+
}
|
|
33854
|
+
async readConversation(workspaceId, afterSeq, limit) {
|
|
33855
|
+
const rows = await this.sql.unsafe(`SELECT * FROM ${this.t("workspace_conversation_messages")}
|
|
33856
|
+
WHERE workspace_id = $1 AND seq > $2 ORDER BY seq ASC LIMIT $3`, [workspaceId, afterSeq, limit]);
|
|
33857
|
+
return rows.map((row) => this.conversationMessageFromRow(row));
|
|
33858
|
+
}
|
|
33859
|
+
async getConversationState(workspaceId) {
|
|
33860
|
+
const rows = await this.sql.unsafe(`SELECT * FROM ${this.t("workspace_conversations")} WHERE workspace_id = $1`, [workspaceId]);
|
|
33861
|
+
const row = rows[0];
|
|
33862
|
+
return row ? {
|
|
33863
|
+
workspaceId: String(row.workspace_id),
|
|
33864
|
+
status: row.status,
|
|
33865
|
+
expiresAt: asDateOrNull(row.expires_at),
|
|
33866
|
+
deletedAt: asDateOrNull(row.deleted_at),
|
|
33867
|
+
updatedAt: asDate(row.updated_at)
|
|
33868
|
+
} : null;
|
|
33869
|
+
}
|
|
33870
|
+
async setConversationExpiry(workspaceId, expiresAt, at) {
|
|
33871
|
+
await this.sql.unsafe(`INSERT INTO ${this.t("workspace_conversations")}
|
|
33872
|
+
(workspace_id, status, expires_at, deleted_at, updated_at)
|
|
33873
|
+
VALUES ($1, 'retained', $2, NULL, $3)
|
|
33874
|
+
ON CONFLICT (workspace_id) DO UPDATE
|
|
33875
|
+
SET expires_at = EXCLUDED.expires_at, updated_at = EXCLUDED.updated_at
|
|
33876
|
+
WHERE ${this.t("workspace_conversations")}.status <> 'deleted'`, [workspaceId, expiresAt, at]);
|
|
33877
|
+
}
|
|
33878
|
+
async deleteConversation(workspaceId, at) {
|
|
33879
|
+
await this.sql.begin(async (tx) => {
|
|
33880
|
+
await tx.unsafe(`DELETE FROM ${this.t("workspace_conversation_messages")} WHERE workspace_id = $1`, [workspaceId]);
|
|
33881
|
+
await tx.unsafe(`INSERT INTO ${this.t("workspace_conversations")}
|
|
33882
|
+
(workspace_id, status, expires_at, deleted_at, updated_at)
|
|
33883
|
+
VALUES ($1, 'deleted', NULL, $2, $2)
|
|
33884
|
+
ON CONFLICT (workspace_id) DO UPDATE
|
|
33885
|
+
SET status = 'deleted', expires_at = NULL, deleted_at = EXCLUDED.deleted_at,
|
|
33886
|
+
updated_at = EXCLUDED.updated_at`, [workspaceId, at]);
|
|
33887
|
+
});
|
|
33888
|
+
}
|
|
33889
|
+
async pruneExpiredConversations(at) {
|
|
33890
|
+
const rows = await this.sql.unsafe(`DELETE FROM ${this.t("workspace_conversation_messages")} AS messages
|
|
33891
|
+
USING ${this.t("workspace_conversations")} AS conversations
|
|
33892
|
+
WHERE messages.workspace_id = conversations.workspace_id
|
|
33893
|
+
AND conversations.status = 'retained'
|
|
33894
|
+
AND conversations.expires_at IS NOT NULL
|
|
33895
|
+
AND conversations.expires_at <= $1
|
|
33896
|
+
RETURNING messages.workspace_id`, [at]);
|
|
33897
|
+
return new Set(rows.map((row) => String(row.workspace_id))).size;
|
|
33898
|
+
}
|
|
33193
33899
|
outboxFromRow(r) {
|
|
33194
33900
|
return {
|
|
33195
33901
|
id: String(r.id),
|
|
@@ -33227,7 +33933,7 @@ class PostgresStore {
|
|
|
33227
33933
|
async appendEvent(workspaceId, eventType, payload, at) {
|
|
33228
33934
|
await this.sql.unsafe(`INSERT INTO ${this.t("event_outbox")}
|
|
33229
33935
|
(id, workspace_id, event_type, payload, occurred_at, next_attempt_at)
|
|
33230
|
-
VALUES ($1, $2, $3, $4::jsonb, $5, $5)`, [
|
|
33936
|
+
VALUES ($1, $2, $3, $4::jsonb, $5, $5)`, [randomUUID5(), workspaceId, eventType, JSON.stringify(payload), at]);
|
|
33231
33937
|
}
|
|
33232
33938
|
}
|
|
33233
33939
|
// src/index.ts
|
|
@@ -38085,7 +38791,7 @@ var yargs_default = Yargs;
|
|
|
38085
38791
|
|
|
38086
38792
|
// src/server-process.ts
|
|
38087
38793
|
import { spawn, spawnSync } from "child_process";
|
|
38088
|
-
import { createHash as createHash5, randomUUID as
|
|
38794
|
+
import { createHash as createHash5, randomUUID as randomUUID12 } from "crypto";
|
|
38089
38795
|
import {
|
|
38090
38796
|
closeSync,
|
|
38091
38797
|
existsSync,
|
|
@@ -38103,10 +38809,10 @@ import { dirname as dirname5, join as join4, resolve as resolve7 } from "path";
|
|
|
38103
38809
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
38104
38810
|
|
|
38105
38811
|
// ../../apps/pocketcoder-server/src/persistence.ts
|
|
38106
|
-
import { randomUUID as
|
|
38812
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
38107
38813
|
|
|
38108
38814
|
// ../../apps/pocketcoder-server/src/service.ts
|
|
38109
|
-
import { randomUUID as
|
|
38815
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
38110
38816
|
function templateAuthorized(principal, templateName) {
|
|
38111
38817
|
return principal.templateNames.includes("*") || principal.templateNames.includes(templateName);
|
|
38112
38818
|
}
|
|
@@ -38124,6 +38830,7 @@ function toResource(row) {
|
|
|
38124
38830
|
agent_state: row.agentState,
|
|
38125
38831
|
change_cursor: row.changeSeq,
|
|
38126
38832
|
provider_kind: row.providerKind,
|
|
38833
|
+
provisioning_mode: row.provisioningMode,
|
|
38127
38834
|
health: row.health,
|
|
38128
38835
|
created_at: row.createdAt.toISOString(),
|
|
38129
38836
|
updated_at: row.updatedAt.toISOString(),
|
|
@@ -38143,6 +38850,7 @@ function toResource(row) {
|
|
|
38143
38850
|
persistence: {
|
|
38144
38851
|
enabled: row.templateSnapshot.spec.persistence.mounts.length > 0,
|
|
38145
38852
|
conversation_restore: row.persistenceCapability,
|
|
38853
|
+
conversation_resume: row.persistenceCapability === "supported" ? { status: "supported", reason: null } : row.persistenceCapability === "filesystem_only" ? { status: "unsupported", reason: "filesystem_only" } : { status: "unknown", reason: "capability_unknown" },
|
|
38146
38854
|
latest_checkpoint_id: row.latestCheckpointId
|
|
38147
38855
|
},
|
|
38148
38856
|
outputs: row.outputs,
|
|
@@ -38204,7 +38912,7 @@ class WorkspaceService {
|
|
|
38204
38912
|
}
|
|
38205
38913
|
const now = this.now();
|
|
38206
38914
|
const result = await store2.insertWorkspace({
|
|
38207
|
-
id:
|
|
38915
|
+
id: randomUUID6(),
|
|
38208
38916
|
principalId: principal.id,
|
|
38209
38917
|
externalId: body.external_id,
|
|
38210
38918
|
idempotencyKey,
|
|
@@ -38361,8 +39069,8 @@ class PersistenceService {
|
|
|
38361
39069
|
}
|
|
38362
39070
|
this.storageDriver();
|
|
38363
39071
|
const requestDigest = digestOf({ workspace_id: workspaceId, ...body });
|
|
38364
|
-
const checkpointId =
|
|
38365
|
-
const operationId =
|
|
39072
|
+
const checkpointId = randomUUID7();
|
|
39073
|
+
const operationId = randomUUID7();
|
|
38366
39074
|
const now = this.now();
|
|
38367
39075
|
const operationResult = await this.deps.store.insertOperation({
|
|
38368
39076
|
id: operationId,
|
|
@@ -38484,6 +39192,7 @@ class PersistenceService {
|
|
|
38484
39192
|
}
|
|
38485
39193
|
async pruneExpired() {
|
|
38486
39194
|
const now = this.now();
|
|
39195
|
+
const transcriptsDeleted = await this.deps.store.pruneExpiredConversations(now);
|
|
38487
39196
|
let deleted = 0;
|
|
38488
39197
|
let skipped = 0;
|
|
38489
39198
|
for (const principal of await this.deps.store.listPrincipals()) {
|
|
@@ -38505,7 +39214,7 @@ class PersistenceService {
|
|
|
38505
39214
|
}
|
|
38506
39215
|
}
|
|
38507
39216
|
}
|
|
38508
|
-
return { deleted, skipped };
|
|
39217
|
+
return { deleted, skipped, transcriptsDeleted };
|
|
38509
39218
|
}
|
|
38510
39219
|
async stopForSnapshot(workspace2, operationId) {
|
|
38511
39220
|
const hook = workspace2.templateSnapshot.spec.checkpointHook;
|
|
@@ -38530,6 +39239,7 @@ class PersistenceService {
|
|
|
38530
39239
|
async completePreserve(workspace2, checkpoint, storage, operationId, result, quiesced) {
|
|
38531
39240
|
const { store: store2, driver: driver2, hub } = this.deps;
|
|
38532
39241
|
const readyAt = this.now();
|
|
39242
|
+
const conversationRestore = quiesced ? workspace2.persistenceCapability : "filesystem_only";
|
|
38533
39243
|
await store2.updateCheckpoint(checkpoint.id, {
|
|
38534
39244
|
state: "ready",
|
|
38535
39245
|
providerKind: this.storageDriver().kind,
|
|
@@ -38539,7 +39249,7 @@ class PersistenceService {
|
|
|
38539
39249
|
logicalBytes: result.manifest.logical_bytes,
|
|
38540
39250
|
storedBytes: result.storedBytes,
|
|
38541
39251
|
fileCount: result.manifest.file_count,
|
|
38542
|
-
conversationRestore
|
|
39252
|
+
conversationRestore,
|
|
38543
39253
|
readyAt
|
|
38544
39254
|
}, readyAt);
|
|
38545
39255
|
const readyCheckpoint = await store2.getCheckpoint(checkpoint.id);
|
|
@@ -38551,7 +39261,7 @@ class PersistenceService {
|
|
|
38551
39261
|
fileCount: result.manifest.file_count,
|
|
38552
39262
|
retainedUntil: checkpoint.expiresAt
|
|
38553
39263
|
}, readyAt);
|
|
38554
|
-
await store2.updateWorkspace(workspace2.id, { latestCheckpointId: checkpoint.id }, readyAt);
|
|
39264
|
+
await store2.updateWorkspace(workspace2.id, { latestCheckpointId: checkpoint.id, persistenceCapability: conversationRestore }, readyAt);
|
|
38555
39265
|
if (workspace2.providerRef) {
|
|
38556
39266
|
await driver2.remove({
|
|
38557
39267
|
kind: workspace2.providerKind ?? "",
|
|
@@ -38634,9 +39344,9 @@ class PersistenceService {
|
|
|
38634
39344
|
const now = this.now();
|
|
38635
39345
|
const requestDigest = digestOf({ checkpoint_id: checkpointId, ...body });
|
|
38636
39346
|
await this.assertOperationCapacity(principal.id, "restore", idempotencyKey);
|
|
38637
|
-
const resultWorkspaceId =
|
|
39347
|
+
const resultWorkspaceId = randomUUID7();
|
|
38638
39348
|
const operationResult = await this.deps.store.insertOperation({
|
|
38639
|
-
id:
|
|
39349
|
+
id: randomUUID7(),
|
|
38640
39350
|
principalId: principal.id,
|
|
38641
39351
|
kind: "restore",
|
|
38642
39352
|
state: "pending",
|
|
@@ -38701,7 +39411,7 @@ class PersistenceService {
|
|
|
38701
39411
|
throw new ApiError("idempotency.conflict", "The requested external_id is already active.");
|
|
38702
39412
|
}
|
|
38703
39413
|
await this.deps.store.appendEvent(checkpoint.workspaceId, "workspace.restore_queued", {
|
|
38704
|
-
id:
|
|
39414
|
+
id: randomUUID7(),
|
|
38705
39415
|
type: "workspace.restore_queued",
|
|
38706
39416
|
occurred_at: now.toISOString(),
|
|
38707
39417
|
origin_workspace_id: checkpoint.workspaceId,
|
|
@@ -38722,7 +39432,7 @@ class PersistenceService {
|
|
|
38722
39432
|
const now = this.now();
|
|
38723
39433
|
await this.assertOperationCapacity(principal.id, "verify", idempotencyKey);
|
|
38724
39434
|
const inserted = await this.deps.store.insertOperation({
|
|
38725
|
-
id:
|
|
39435
|
+
id: randomUUID7(),
|
|
38726
39436
|
principalId: principal.id,
|
|
38727
39437
|
kind: "verify",
|
|
38728
39438
|
state: "running",
|
|
@@ -38762,7 +39472,7 @@ class PersistenceService {
|
|
|
38762
39472
|
const now = this.now();
|
|
38763
39473
|
await this.assertOperationCapacity(principal.id, "delete", idempotencyKey);
|
|
38764
39474
|
const inserted = await this.deps.store.insertOperation({
|
|
38765
|
-
id:
|
|
39475
|
+
id: randomUUID7(),
|
|
38766
39476
|
principalId: principal.id,
|
|
38767
39477
|
kind: "delete",
|
|
38768
39478
|
state: "running",
|
|
@@ -38846,7 +39556,7 @@ class PersistenceService {
|
|
|
38846
39556
|
});
|
|
38847
39557
|
const at = this.now();
|
|
38848
39558
|
await this.deps.store.appendEvent(workspaceId, "workspace.output_published", {
|
|
38849
|
-
id:
|
|
39559
|
+
id: randomUUID7(),
|
|
38850
39560
|
type: "workspace.output_published",
|
|
38851
39561
|
occurred_at: at.toISOString(),
|
|
38852
39562
|
workspace_id: workspaceId,
|
|
@@ -38890,7 +39600,7 @@ class PersistenceService {
|
|
|
38890
39600
|
async emitCheckpointEvent(type, row) {
|
|
38891
39601
|
const at = this.now();
|
|
38892
39602
|
await this.deps.store.appendEvent(row.workspaceId, type, {
|
|
38893
|
-
id:
|
|
39603
|
+
id: randomUUID7(),
|
|
38894
39604
|
type,
|
|
38895
39605
|
occurred_at: at.toISOString(),
|
|
38896
39606
|
checkpoint: {
|
|
@@ -38907,6 +39617,50 @@ class PersistenceService {
|
|
|
38907
39617
|
}
|
|
38908
39618
|
|
|
38909
39619
|
// ../../apps/pocketcoder-server/src/config.ts
|
|
39620
|
+
function resolveWarmPools2(env2) {
|
|
39621
|
+
const raw = env2.POCKETCODER_WARM_POOLS;
|
|
39622
|
+
if (!raw)
|
|
39623
|
+
return [];
|
|
39624
|
+
let value;
|
|
39625
|
+
try {
|
|
39626
|
+
value = JSON.parse(raw);
|
|
39627
|
+
} catch {
|
|
39628
|
+
throw new Error("POCKETCODER_WARM_POOLS must be valid JSON");
|
|
39629
|
+
}
|
|
39630
|
+
if (!Array.isArray(value))
|
|
39631
|
+
throw new Error("POCKETCODER_WARM_POOLS must be a JSON array");
|
|
39632
|
+
return value.map((item, index2) => {
|
|
39633
|
+
if (!item || typeof item !== "object")
|
|
39634
|
+
throw new Error(`POCKETCODER_WARM_POOLS[${index2}] must be an object`);
|
|
39635
|
+
const entry = item;
|
|
39636
|
+
if (typeof entry.template !== "string" || !entry.template)
|
|
39637
|
+
throw new Error(`POCKETCODER_WARM_POOLS[${index2}].template is required`);
|
|
39638
|
+
const minReady = entry.min_ready === undefined ? 1 : Number(entry.min_ready);
|
|
39639
|
+
if (!Number.isInteger(minReady) || minReady <= 0)
|
|
39640
|
+
throw new Error(`POCKETCODER_WARM_POOLS[${index2}].min_ready must be a positive integer`);
|
|
39641
|
+
const missPolicy = entry.miss_policy ?? "cold";
|
|
39642
|
+
if (missPolicy !== "cold" && missPolicy !== "wait")
|
|
39643
|
+
throw new Error(`POCKETCODER_WARM_POOLS[${index2}].miss_policy must be cold or wait`);
|
|
39644
|
+
const duration4 = (key, fallback) => {
|
|
39645
|
+
const candidate = entry[key] ?? fallback;
|
|
39646
|
+
if (typeof candidate !== "string")
|
|
39647
|
+
throw new Error(`POCKETCODER_WARM_POOLS[${index2}].${key} must be a duration`);
|
|
39648
|
+
try {
|
|
39649
|
+
return parseDurationMs(candidate);
|
|
39650
|
+
} catch {
|
|
39651
|
+
throw new Error(`POCKETCODER_WARM_POOLS[${index2}].${key} must be a duration`);
|
|
39652
|
+
}
|
|
39653
|
+
};
|
|
39654
|
+
return {
|
|
39655
|
+
template: entry.template,
|
|
39656
|
+
...typeof entry.version === "string" ? { version: entry.version } : {},
|
|
39657
|
+
minReady,
|
|
39658
|
+
maxWarmAgeMs: duration4("max_warm_age", "15m"),
|
|
39659
|
+
missPolicy,
|
|
39660
|
+
waitTimeoutMs: duration4("wait_timeout", "5s")
|
|
39661
|
+
};
|
|
39662
|
+
});
|
|
39663
|
+
}
|
|
38910
39664
|
function intEnv(env2, key, fallback) {
|
|
38911
39665
|
const raw = env2[key];
|
|
38912
39666
|
if (!raw)
|
|
@@ -39030,7 +39784,8 @@ function loadConfig(env2 = process.env) {
|
|
|
39030
39784
|
limits: resolveAdmissionLimits(env2),
|
|
39031
39785
|
schedulerIntervalMs: intEnv(env2, "POCKETCODER_SCHEDULER_INTERVAL_MS", 1000),
|
|
39032
39786
|
outboxIntervalMs: intEnv(env2, "POCKETCODER_OUTBOX_INTERVAL_MS", 1000),
|
|
39033
|
-
persistenceLimits: resolvePersistenceLimits(env2)
|
|
39787
|
+
persistenceLimits: resolvePersistenceLimits(env2),
|
|
39788
|
+
warmPools: resolveWarmPools2(env2)
|
|
39034
39789
|
};
|
|
39035
39790
|
}
|
|
39036
39791
|
|
|
@@ -39040,6 +39795,7 @@ import { tmpdir } from "os";
|
|
|
39040
39795
|
import { join as join2 } from "path";
|
|
39041
39796
|
var WORKSPACE_LABEL = "pocketcoder.workspace";
|
|
39042
39797
|
var DIGEST_LABEL = "pocketcoder.template-digest";
|
|
39798
|
+
var POOL_RUNTIME_LABEL = "pocketcoder.pool-runtime";
|
|
39043
39799
|
async function run(bin, args) {
|
|
39044
39800
|
const proc = Bun.spawn([bin, ...args], { stdout: "pipe", stderr: "pipe" });
|
|
39045
39801
|
const [stdout, stderr, code] = await Promise.all([
|
|
@@ -39148,6 +39904,56 @@ class DockerDriver {
|
|
|
39148
39904
|
throw err;
|
|
39149
39905
|
}
|
|
39150
39906
|
}
|
|
39907
|
+
async createWarm(launch) {
|
|
39908
|
+
const spec = launch.template.spec;
|
|
39909
|
+
await mkdir(this.opts.inputDir, { recursive: true, mode: 448 });
|
|
39910
|
+
await chmod(this.opts.inputDir, 448);
|
|
39911
|
+
const inputFile = this.inputPath(`pool-${launch.runtimeId}`);
|
|
39912
|
+
await writeFile2(inputFile, JSON.stringify(launch.input), { mode: 420 });
|
|
39913
|
+
const name2 = `pocketcoder-pool-${launch.runtimeId}`;
|
|
39914
|
+
const args = [
|
|
39915
|
+
"run",
|
|
39916
|
+
"--detach",
|
|
39917
|
+
"--name",
|
|
39918
|
+
name2,
|
|
39919
|
+
"--label",
|
|
39920
|
+
`${POOL_RUNTIME_LABEL}=${launch.runtimeId}`,
|
|
39921
|
+
"--label",
|
|
39922
|
+
`${DIGEST_LABEL}=${launch.template.digest}`,
|
|
39923
|
+
"--restart=no",
|
|
39924
|
+
"--user",
|
|
39925
|
+
`${spec.security.uid}:${spec.security.gid}`,
|
|
39926
|
+
"--security-opt",
|
|
39927
|
+
"no-new-privileges",
|
|
39928
|
+
"--cpus",
|
|
39929
|
+
spec.resources.cpu.endsWith("m") ? String(Number(spec.resources.cpu.slice(0, -1)) / 1000) : spec.resources.cpu,
|
|
39930
|
+
"--memory",
|
|
39931
|
+
spec.resources.memory.replace("Mi", "m").replace("Gi", "g"),
|
|
39932
|
+
"-v",
|
|
39933
|
+
`${inputFile}:/run/pocketcoder/input:ro`
|
|
39934
|
+
];
|
|
39935
|
+
for (const cap of spec.security.dropCapabilities)
|
|
39936
|
+
args.push("--cap-drop", cap);
|
|
39937
|
+
if (spec.security.readOnlyRoot)
|
|
39938
|
+
args.push("--read-only");
|
|
39939
|
+
for (const path of spec.security.writableMemoryPaths) {
|
|
39940
|
+
args.push("--tmpfs", `${path}:rw,noexec,nosuid,size=256m,uid=${spec.security.uid},gid=${spec.security.gid},mode=0700`);
|
|
39941
|
+
}
|
|
39942
|
+
if (this.opts.network)
|
|
39943
|
+
args.push("--network", this.opts.network);
|
|
39944
|
+
if (this.opts.addHostGateway)
|
|
39945
|
+
args.push("--add-host", "host.docker.internal:host-gateway");
|
|
39946
|
+
for (const [key, value] of Object.entries(spec.env))
|
|
39947
|
+
args.push("-e", `${key}=${value}`);
|
|
39948
|
+
args.push(await resolveDockerImage(this.opts.dockerBin, spec.image), ...spec.command);
|
|
39949
|
+
try {
|
|
39950
|
+
const id = await run(this.opts.dockerBin, args);
|
|
39951
|
+
return { kind: this.kind, id, name: name2, poolRuntimeId: launch.runtimeId };
|
|
39952
|
+
} catch (error51) {
|
|
39953
|
+
await rm(inputFile, { force: true });
|
|
39954
|
+
throw error51;
|
|
39955
|
+
}
|
|
39956
|
+
}
|
|
39151
39957
|
async inspect(ref) {
|
|
39152
39958
|
try {
|
|
39153
39959
|
const out = await run(this.opts.dockerBin, [
|
|
@@ -39176,6 +39982,11 @@ class DockerDriver {
|
|
|
39176
39982
|
await run(this.opts.dockerBin, ["rm", "-f", ref.id]);
|
|
39177
39983
|
} catch {}
|
|
39178
39984
|
const name2 = typeof ref.name === "string" ? ref.name : "";
|
|
39985
|
+
const poolRuntimeId = typeof ref.poolRuntimeId === "string" ? ref.poolRuntimeId : "";
|
|
39986
|
+
if (poolRuntimeId) {
|
|
39987
|
+
await rm(this.inputPath(`pool-${poolRuntimeId}`), { force: true });
|
|
39988
|
+
return;
|
|
39989
|
+
}
|
|
39179
39990
|
const workspaceId = name2.replace(/^pocketcoder-ws-/, "");
|
|
39180
39991
|
if (workspaceId) {
|
|
39181
39992
|
await rm(this.inputPath(workspaceId), { force: true });
|
|
@@ -39184,6 +39995,9 @@ class DockerDriver {
|
|
|
39184
39995
|
async cleanupInput(workspaceId) {
|
|
39185
39996
|
await rm(this.inputPath(workspaceId), { force: true });
|
|
39186
39997
|
}
|
|
39998
|
+
async cleanupWarmInput(runtimeId) {
|
|
39999
|
+
await rm(this.inputPath(`pool-${runtimeId}`), { force: true });
|
|
40000
|
+
}
|
|
39187
40001
|
async list() {
|
|
39188
40002
|
const out = await run(this.opts.dockerBin, [
|
|
39189
40003
|
"ps",
|
|
@@ -39205,6 +40019,32 @@ class DockerDriver {
|
|
|
39205
40019
|
};
|
|
39206
40020
|
});
|
|
39207
40021
|
}
|
|
40022
|
+
async listWarm() {
|
|
40023
|
+
const out = await run(this.opts.dockerBin, [
|
|
40024
|
+
"ps",
|
|
40025
|
+
"--all",
|
|
40026
|
+
"--filter",
|
|
40027
|
+
`label=${POOL_RUNTIME_LABEL}`,
|
|
40028
|
+
"--format",
|
|
40029
|
+
`{{.ID}} {{.Label "${POOL_RUNTIME_LABEL}"}} {{.Label "${DIGEST_LABEL}"}}`
|
|
40030
|
+
]);
|
|
40031
|
+
if (!out)
|
|
40032
|
+
return [];
|
|
40033
|
+
return out.split(`
|
|
40034
|
+
`).map((line4) => {
|
|
40035
|
+
const [id = "", runtimeId = "", templateDigest = ""] = line4.split("\t");
|
|
40036
|
+
return {
|
|
40037
|
+
runtimeId,
|
|
40038
|
+
templateDigest,
|
|
40039
|
+
ref: {
|
|
40040
|
+
kind: this.kind,
|
|
40041
|
+
id,
|
|
40042
|
+
name: `pocketcoder-pool-${runtimeId}`,
|
|
40043
|
+
poolRuntimeId: runtimeId
|
|
40044
|
+
}
|
|
40045
|
+
};
|
|
40046
|
+
});
|
|
40047
|
+
}
|
|
39208
40048
|
}
|
|
39209
40049
|
// ../drivers/src/file-secrets.ts
|
|
39210
40050
|
import { lstat, realpath } from "fs/promises";
|
|
@@ -39761,6 +40601,7 @@ class FilesystemStorageDriver {
|
|
|
39761
40601
|
// ../drivers/src/kubernetes.ts
|
|
39762
40602
|
var KUBERNETES_WORKSPACE_LABEL = "pocketcoder.workspace";
|
|
39763
40603
|
var KUBERNETES_DIGEST_ANNOTATION = "pocketcoder.dev/template-digest";
|
|
40604
|
+
var KUBERNETES_POOL_LABEL = "pocketcoder.pool-runtime";
|
|
39764
40605
|
async function kubectl(bin, namespace, args, input) {
|
|
39765
40606
|
const proc = Bun.spawn([bin, "--namespace", namespace, "--request-timeout=30s", ...args], {
|
|
39766
40607
|
stdout: "pipe",
|
|
@@ -39989,6 +40830,102 @@ class KubernetesDriver {
|
|
|
39989
40830
|
throw error51;
|
|
39990
40831
|
}
|
|
39991
40832
|
}
|
|
40833
|
+
async createWarm(launch) {
|
|
40834
|
+
const spec = launch.template.spec;
|
|
40835
|
+
const name2 = `pocketcoder-pool-${launch.runtimeId}`;
|
|
40836
|
+
const inputSecret = `${name2}-input`;
|
|
40837
|
+
await kubectl(this.kubectlBin, this.namespace, ["apply", "-f", "-"], JSON.stringify({
|
|
40838
|
+
apiVersion: "v1",
|
|
40839
|
+
kind: "Secret",
|
|
40840
|
+
metadata: { name: inputSecret, labels: { [KUBERNETES_POOL_LABEL]: launch.runtimeId } },
|
|
40841
|
+
type: "Opaque",
|
|
40842
|
+
stringData: { "input.json": JSON.stringify(launch.input) }
|
|
40843
|
+
}));
|
|
40844
|
+
const memory = spec.security.writableMemoryPaths.map((path, index2) => ({
|
|
40845
|
+
volume: { name: `memory-${index2}`, emptyDir: { medium: "Memory", sizeLimit: "256Mi" } },
|
|
40846
|
+
mount: { name: `memory-${index2}`, mountPath: path }
|
|
40847
|
+
}));
|
|
40848
|
+
const labels = { [KUBERNETES_POOL_LABEL]: launch.runtimeId };
|
|
40849
|
+
const annotations = { [KUBERNETES_DIGEST_ANNOTATION]: launch.template.digest };
|
|
40850
|
+
const manifest = {
|
|
40851
|
+
apiVersion: "batch/v1",
|
|
40852
|
+
kind: "Job",
|
|
40853
|
+
metadata: { name: name2, labels, annotations },
|
|
40854
|
+
spec: {
|
|
40855
|
+
backoffLimit: 0,
|
|
40856
|
+
ttlSecondsAfterFinished: 3600,
|
|
40857
|
+
template: {
|
|
40858
|
+
metadata: { labels, annotations },
|
|
40859
|
+
spec: {
|
|
40860
|
+
restartPolicy: "Never",
|
|
40861
|
+
automountServiceAccountToken: false,
|
|
40862
|
+
...this.serviceAccountName ? { serviceAccountName: this.serviceAccountName } : {},
|
|
40863
|
+
securityContext: {
|
|
40864
|
+
runAsUser: spec.security.uid,
|
|
40865
|
+
runAsGroup: spec.security.gid,
|
|
40866
|
+
runAsNonRoot: true,
|
|
40867
|
+
fsGroup: spec.security.gid,
|
|
40868
|
+
fsGroupChangePolicy: "OnRootMismatch",
|
|
40869
|
+
seccompProfile: { type: spec.security.seccomp }
|
|
40870
|
+
},
|
|
40871
|
+
containers: [
|
|
40872
|
+
{
|
|
40873
|
+
name: "workspace",
|
|
40874
|
+
image: spec.image,
|
|
40875
|
+
imagePullPolicy: this.imagePullPolicy,
|
|
40876
|
+
command: spec.command,
|
|
40877
|
+
env: Object.entries(spec.env).map(([name3, value]) => ({ name: name3, value })),
|
|
40878
|
+
resources: { requests: spec.resources, limits: spec.resources },
|
|
40879
|
+
securityContext: {
|
|
40880
|
+
readOnlyRootFilesystem: spec.security.readOnlyRoot,
|
|
40881
|
+
allowPrivilegeEscalation: spec.security.allowPrivilegeEscalation,
|
|
40882
|
+
capabilities: { drop: spec.security.dropCapabilities }
|
|
40883
|
+
},
|
|
40884
|
+
volumeMounts: [
|
|
40885
|
+
{
|
|
40886
|
+
name: "provider-input",
|
|
40887
|
+
mountPath: "/run/pocketcoder/input",
|
|
40888
|
+
subPath: "input.json",
|
|
40889
|
+
readOnly: true
|
|
40890
|
+
},
|
|
40891
|
+
...memory.map((item) => item.mount)
|
|
40892
|
+
]
|
|
40893
|
+
}
|
|
40894
|
+
],
|
|
40895
|
+
volumes: [
|
|
40896
|
+
{
|
|
40897
|
+
name: "provider-input",
|
|
40898
|
+
secret: {
|
|
40899
|
+
secretName: inputSecret,
|
|
40900
|
+
items: [{ key: "input.json", path: "input.json" }]
|
|
40901
|
+
}
|
|
40902
|
+
},
|
|
40903
|
+
...memory.map((item) => item.volume)
|
|
40904
|
+
]
|
|
40905
|
+
}
|
|
40906
|
+
}
|
|
40907
|
+
}
|
|
40908
|
+
};
|
|
40909
|
+
try {
|
|
40910
|
+
await kubectl(this.kubectlBin, this.namespace, ["apply", "-f", "-"], JSON.stringify(manifest));
|
|
40911
|
+
return {
|
|
40912
|
+
kind: this.kind,
|
|
40913
|
+
id: name2,
|
|
40914
|
+
name: name2,
|
|
40915
|
+
inputSecret,
|
|
40916
|
+
namespace: this.namespace,
|
|
40917
|
+
poolRuntimeId: launch.runtimeId
|
|
40918
|
+
};
|
|
40919
|
+
} catch (error51) {
|
|
40920
|
+
await kubectl(this.kubectlBin, this.namespace, [
|
|
40921
|
+
"delete",
|
|
40922
|
+
"secret",
|
|
40923
|
+
inputSecret,
|
|
40924
|
+
"--ignore-not-found"
|
|
40925
|
+
]).catch(() => {});
|
|
40926
|
+
throw error51;
|
|
40927
|
+
}
|
|
40928
|
+
}
|
|
39992
40929
|
async inspect(ref) {
|
|
39993
40930
|
try {
|
|
39994
40931
|
const output = await kubectl(this.kubectlBin, this.namespace, [
|
|
@@ -40022,7 +40959,7 @@ class KubernetesDriver {
|
|
|
40022
40959
|
"delete",
|
|
40023
40960
|
"pod",
|
|
40024
40961
|
"-l",
|
|
40025
|
-
|
|
40962
|
+
`job-name=${ref.id}`,
|
|
40026
40963
|
`--grace-period=${graceSeconds}`,
|
|
40027
40964
|
"--wait=true",
|
|
40028
40965
|
"--ignore-not-found"
|
|
@@ -40052,6 +40989,14 @@ class KubernetesDriver {
|
|
|
40052
40989
|
"--ignore-not-found"
|
|
40053
40990
|
]).catch(() => {});
|
|
40054
40991
|
}
|
|
40992
|
+
async cleanupWarmInput(runtimeId) {
|
|
40993
|
+
await kubectl(this.kubectlBin, this.namespace, [
|
|
40994
|
+
"delete",
|
|
40995
|
+
"secret",
|
|
40996
|
+
`pocketcoder-pool-${runtimeId}-input`,
|
|
40997
|
+
"--ignore-not-found"
|
|
40998
|
+
]).catch(() => {});
|
|
40999
|
+
}
|
|
40055
41000
|
async list() {
|
|
40056
41001
|
const output = await kubectl(this.kubectlBin, this.namespace, [
|
|
40057
41002
|
"get",
|
|
@@ -40083,6 +41028,38 @@ class KubernetesDriver {
|
|
|
40083
41028
|
];
|
|
40084
41029
|
});
|
|
40085
41030
|
}
|
|
41031
|
+
async listWarm() {
|
|
41032
|
+
const output = await kubectl(this.kubectlBin, this.namespace, [
|
|
41033
|
+
"get",
|
|
41034
|
+
"jobs",
|
|
41035
|
+
"-l",
|
|
41036
|
+
KUBERNETES_POOL_LABEL,
|
|
41037
|
+
"-o",
|
|
41038
|
+
"json"
|
|
41039
|
+
]);
|
|
41040
|
+
const list = JSON.parse(output);
|
|
41041
|
+
return (list.items ?? []).flatMap((job) => {
|
|
41042
|
+
const runtimeId = job.metadata?.labels?.[KUBERNETES_POOL_LABEL];
|
|
41043
|
+
const name2 = job.metadata?.name;
|
|
41044
|
+
const templateDigest = job.metadata?.annotations?.[KUBERNETES_DIGEST_ANNOTATION];
|
|
41045
|
+
if (!runtimeId || !name2 || !templateDigest)
|
|
41046
|
+
return [];
|
|
41047
|
+
return [
|
|
41048
|
+
{
|
|
41049
|
+
runtimeId,
|
|
41050
|
+
templateDigest,
|
|
41051
|
+
ref: {
|
|
41052
|
+
kind: this.kind,
|
|
41053
|
+
id: name2,
|
|
41054
|
+
name: name2,
|
|
41055
|
+
inputSecret: `${name2}-input`,
|
|
41056
|
+
namespace: this.namespace,
|
|
41057
|
+
poolRuntimeId: runtimeId
|
|
41058
|
+
}
|
|
41059
|
+
}
|
|
41060
|
+
];
|
|
41061
|
+
});
|
|
41062
|
+
}
|
|
40086
41063
|
}
|
|
40087
41064
|
// ../drivers/src/kubernetes-secrets.ts
|
|
40088
41065
|
function collect(workspace2) {
|
|
@@ -40189,13 +41166,18 @@ class KubernetesPvcStorageDriver {
|
|
|
40189
41166
|
class FakeDriver {
|
|
40190
41167
|
kind = "fake";
|
|
40191
41168
|
created = [];
|
|
41169
|
+
warmCreated = [];
|
|
40192
41170
|
terminated = [];
|
|
40193
41171
|
stopped = [];
|
|
40194
41172
|
failNextCreate = false;
|
|
41173
|
+
createDelayMs = 0;
|
|
40195
41174
|
counter = 0;
|
|
40196
41175
|
live = new Map;
|
|
40197
41176
|
stoppedIds = new Set;
|
|
41177
|
+
warm = new Map;
|
|
40198
41178
|
async create(launch) {
|
|
41179
|
+
if (this.createDelayMs > 0)
|
|
41180
|
+
await new Promise((resolve7) => setTimeout(resolve7, this.createDelayMs));
|
|
40199
41181
|
if (this.failNextCreate) {
|
|
40200
41182
|
this.failNextCreate = false;
|
|
40201
41183
|
throw new Error("fake driver create failure");
|
|
@@ -40210,8 +41192,23 @@ class FakeDriver {
|
|
|
40210
41192
|
});
|
|
40211
41193
|
return ref;
|
|
40212
41194
|
}
|
|
41195
|
+
async createWarm(launch) {
|
|
41196
|
+
this.warmCreated.push(launch);
|
|
41197
|
+
this.counter += 1;
|
|
41198
|
+
const ref = {
|
|
41199
|
+
kind: this.kind,
|
|
41200
|
+
id: `fake-warm-${this.counter}`,
|
|
41201
|
+
poolRuntimeId: launch.runtimeId
|
|
41202
|
+
};
|
|
41203
|
+
this.warm.set(launch.runtimeId, {
|
|
41204
|
+
runtimeId: launch.runtimeId,
|
|
41205
|
+
templateDigest: launch.template.digest,
|
|
41206
|
+
ref
|
|
41207
|
+
});
|
|
41208
|
+
return ref;
|
|
41209
|
+
}
|
|
40213
41210
|
async inspect(ref) {
|
|
40214
|
-
const exists3 = [...this.live.values()].some((p) => p.ref.id === ref.id);
|
|
41211
|
+
const exists3 = [...this.live.values(), ...this.warm.values()].some((p) => p.ref.id === ref.id);
|
|
40215
41212
|
return {
|
|
40216
41213
|
exists: exists3,
|
|
40217
41214
|
running: exists3 && !this.stoppedIds.has(ref.id),
|
|
@@ -40229,10 +41226,16 @@ class FakeDriver {
|
|
|
40229
41226
|
if (p.ref.id === ref.id)
|
|
40230
41227
|
this.live.delete(wsId);
|
|
40231
41228
|
}
|
|
41229
|
+
for (const [id, p] of this.warm)
|
|
41230
|
+
if (p.ref.id === ref.id)
|
|
41231
|
+
this.warm.delete(id);
|
|
40232
41232
|
}
|
|
40233
41233
|
async list() {
|
|
40234
41234
|
return [...this.live.values()];
|
|
40235
41235
|
}
|
|
41236
|
+
async listWarm() {
|
|
41237
|
+
return [...this.warm.values()];
|
|
41238
|
+
}
|
|
40236
41239
|
inputFor(workspaceId) {
|
|
40237
41240
|
return this.created.find((l) => l.workspace.id === workspaceId)?.input;
|
|
40238
41241
|
}
|
|
@@ -40241,7 +41244,7 @@ class FakeDriver {
|
|
|
40241
41244
|
var PLACEHOLDER_DIGEST = "a".repeat(64);
|
|
40242
41245
|
var OTHER_DIGEST = "b".repeat(64);
|
|
40243
41246
|
// ../testkit/src/memory-store.ts
|
|
40244
|
-
import { randomUUID as
|
|
41247
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
40245
41248
|
var ACTIVE_STATES = [
|
|
40246
41249
|
"provisioning",
|
|
40247
41250
|
"connected",
|
|
@@ -40250,6 +41253,8 @@ var ACTIVE_STATES = [
|
|
|
40250
41253
|
"terminating"
|
|
40251
41254
|
];
|
|
40252
41255
|
var MAX_LOG_BYTES2 = 10 * 1024 * 1024;
|
|
41256
|
+
var MAX_CONVERSATION_BYTES2 = 50 * 1024 * 1024;
|
|
41257
|
+
var MAX_CONVERSATION_MESSAGES2 = 1e5;
|
|
40253
41258
|
var CHANGE_PATCH_KEYS = new Set([
|
|
40254
41259
|
"connectedAt",
|
|
40255
41260
|
"disconnectedAt",
|
|
@@ -40259,6 +41264,7 @@ var CHANGE_PATCH_KEYS = new Set([
|
|
|
40259
41264
|
"failureLogTailTruncated",
|
|
40260
41265
|
"failureLastLogSeq",
|
|
40261
41266
|
"resolvedSource",
|
|
41267
|
+
"persistenceCapability",
|
|
40262
41268
|
"latestCheckpointId",
|
|
40263
41269
|
"outputs"
|
|
40264
41270
|
]);
|
|
@@ -40268,6 +41274,7 @@ class MemoryStore {
|
|
|
40268
41274
|
principals = [];
|
|
40269
41275
|
keys = [];
|
|
40270
41276
|
workspaces = new Map;
|
|
41277
|
+
warmPoolRuntimes = new Map;
|
|
40271
41278
|
history = [];
|
|
40272
41279
|
outbox = [];
|
|
40273
41280
|
logs = new Map;
|
|
@@ -40277,6 +41284,9 @@ class MemoryStore {
|
|
|
40277
41284
|
checkpoints = new Map;
|
|
40278
41285
|
operations = new Map;
|
|
40279
41286
|
outputs = new Map;
|
|
41287
|
+
conversations = new Map;
|
|
41288
|
+
conversationBytes = new Map;
|
|
41289
|
+
conversationStates = new Map;
|
|
40280
41290
|
changeWaiters = new Map;
|
|
40281
41291
|
async init() {}
|
|
40282
41292
|
async close() {
|
|
@@ -40303,7 +41313,7 @@ class MemoryStore {
|
|
|
40303
41313
|
return { row: existing, created: false, conflict: false };
|
|
40304
41314
|
}
|
|
40305
41315
|
const row = {
|
|
40306
|
-
id:
|
|
41316
|
+
id: randomUUID8(),
|
|
40307
41317
|
name: input.name,
|
|
40308
41318
|
version: input.version,
|
|
40309
41319
|
digest: input.digest,
|
|
@@ -40338,12 +41348,58 @@ class MemoryStore {
|
|
|
40338
41348
|
row.retiredAt = status === "retired" ? new Date : null;
|
|
40339
41349
|
}
|
|
40340
41350
|
}
|
|
41351
|
+
async insertWarmPoolRuntime(row) {
|
|
41352
|
+
const existing = this.warmPoolRuntimes.get(row.id);
|
|
41353
|
+
if (existing)
|
|
41354
|
+
return { ...existing };
|
|
41355
|
+
this.warmPoolRuntimes.set(row.id, { ...row });
|
|
41356
|
+
return { ...row };
|
|
41357
|
+
}
|
|
41358
|
+
async getWarmPoolRuntime(id) {
|
|
41359
|
+
const row = this.warmPoolRuntimes.get(id);
|
|
41360
|
+
return row ? { ...row } : null;
|
|
41361
|
+
}
|
|
41362
|
+
async listWarmPoolRuntimes() {
|
|
41363
|
+
return [...this.warmPoolRuntimes.values()].map((row) => ({ ...row }));
|
|
41364
|
+
}
|
|
41365
|
+
async updateWarmPoolRuntime(id, patch, at) {
|
|
41366
|
+
const row = this.warmPoolRuntimes.get(id);
|
|
41367
|
+
if (!row)
|
|
41368
|
+
return;
|
|
41369
|
+
Object.assign(row, patch);
|
|
41370
|
+
row.updatedAt = at;
|
|
41371
|
+
}
|
|
41372
|
+
async claimWarmPoolRuntime(claim) {
|
|
41373
|
+
const workspace2 = this.workspaces.get(claim.workspaceId);
|
|
41374
|
+
if (workspace2?.state !== "queued")
|
|
41375
|
+
return null;
|
|
41376
|
+
const runtime = [...this.warmPoolRuntimes.values()].filter((row) => row.state === "ready" && row.templateDigest === claim.templateDigest && row.driverKind === claim.driverKind && row.eligibilityFingerprint === claim.eligibilityFingerprint).sort((a, b) => (a.readyAt?.getTime() ?? 0) - (b.readyAt?.getTime() ?? 0))[0];
|
|
41377
|
+
if (!runtime?.providerRef)
|
|
41378
|
+
return null;
|
|
41379
|
+
runtime.state = "leasing";
|
|
41380
|
+
runtime.workspaceId = workspace2.id;
|
|
41381
|
+
runtime.leasedAt = claim.at;
|
|
41382
|
+
runtime.updatedAt = claim.at;
|
|
41383
|
+
workspace2.state = "provisioning";
|
|
41384
|
+
workspace2.provisioningMode = "warm";
|
|
41385
|
+
workspace2.providerKind = runtime.driverKind;
|
|
41386
|
+
workspace2.providerRef = runtime.providerRef;
|
|
41387
|
+
workspace2.registrationDigest = claim.registrationDigest;
|
|
41388
|
+
workspace2.registrationExpiresAt = claim.registrationExpiresAt;
|
|
41389
|
+
workspace2.launchAttempts += 1;
|
|
41390
|
+
workspace2.changeSeq += 1;
|
|
41391
|
+
workspace2.updatedAt = claim.at;
|
|
41392
|
+
this.appendHistory(workspace2, "queued", "provisioning", null, claim.at);
|
|
41393
|
+
this.appendWorkspaceEvent(workspace2, claim.at);
|
|
41394
|
+
this.notifyWorkspaceChange(workspace2.id);
|
|
41395
|
+
return { runtime: { ...runtime }, workspace: { ...workspace2 } };
|
|
41396
|
+
}
|
|
40341
41397
|
async createPrincipal(name2, scopes2, templateNames) {
|
|
40342
41398
|
if (this.principals.some((p) => p.name === name2)) {
|
|
40343
41399
|
throw new Error(`principal exists: ${name2}`);
|
|
40344
41400
|
}
|
|
40345
41401
|
const row = {
|
|
40346
|
-
id:
|
|
41402
|
+
id: randomUUID8(),
|
|
40347
41403
|
name: name2,
|
|
40348
41404
|
scopes: scopes2,
|
|
40349
41405
|
templateNames,
|
|
@@ -40426,6 +41482,7 @@ class MemoryStore {
|
|
|
40426
41482
|
launchInput: row.launchInput,
|
|
40427
41483
|
providerKind: null,
|
|
40428
41484
|
providerRef: null,
|
|
41485
|
+
provisioningMode: null,
|
|
40429
41486
|
registrationDigest: null,
|
|
40430
41487
|
registrationExpiresAt: null,
|
|
40431
41488
|
reconnectDigest: null,
|
|
@@ -40467,7 +41524,15 @@ class MemoryStore {
|
|
|
40467
41524
|
rows = rows.filter((w) => w.state === filter.state);
|
|
40468
41525
|
if (filter.template)
|
|
40469
41526
|
rows = rows.filter((w) => w.templateName === filter.template);
|
|
40470
|
-
|
|
41527
|
+
if (filter.metadata) {
|
|
41528
|
+
rows = rows.filter((w) => Object.entries(filter.metadata ?? {}).every(([key, value]) => w.metadata[key] === value));
|
|
41529
|
+
}
|
|
41530
|
+
const { createdAfter, createdBefore } = filter;
|
|
41531
|
+
if (createdAfter)
|
|
41532
|
+
rows = rows.filter((w) => w.createdAt >= createdAfter);
|
|
41533
|
+
if (createdBefore)
|
|
41534
|
+
rows = rows.filter((w) => w.createdAt < createdBefore);
|
|
41535
|
+
rows.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || b.id.localeCompare(a.id));
|
|
40471
41536
|
if (filter.cursor) {
|
|
40472
41537
|
const idx = rows.findIndex((w) => w.id === filter.cursor);
|
|
40473
41538
|
if (idx >= 0)
|
|
@@ -40561,6 +41626,16 @@ class MemoryStore {
|
|
|
40561
41626
|
row.terminalAt = req.at;
|
|
40562
41627
|
row.launchInput = null;
|
|
40563
41628
|
row.registrationDigest = null;
|
|
41629
|
+
const current = this.conversationStates.get(id);
|
|
41630
|
+
if (current?.status !== "deleted") {
|
|
41631
|
+
this.conversationStates.set(id, {
|
|
41632
|
+
workspaceId: id,
|
|
41633
|
+
status: "retained",
|
|
41634
|
+
expiresAt: new Date(req.at.getTime() + parseDurationMs(row.templateSnapshot.spec.persistence.conversationRetention)),
|
|
41635
|
+
deletedAt: null,
|
|
41636
|
+
updatedAt: req.at
|
|
41637
|
+
});
|
|
41638
|
+
}
|
|
40564
41639
|
}
|
|
40565
41640
|
this.appendHistory(row, fromState, req.to, row.reasonCode, req.at);
|
|
40566
41641
|
this.appendWorkspaceEvent(row, req.at);
|
|
@@ -40677,7 +41752,7 @@ class MemoryStore {
|
|
|
40677
41752
|
}
|
|
40678
41753
|
appendHistory(row, from, to, reason, at) {
|
|
40679
41754
|
this.history.push({
|
|
40680
|
-
id:
|
|
41755
|
+
id: randomUUID8(),
|
|
40681
41756
|
workspaceId: row.id,
|
|
40682
41757
|
fromState: from,
|
|
40683
41758
|
toState: to,
|
|
@@ -40735,6 +41810,76 @@ class MemoryStore {
|
|
|
40735
41810
|
lastSeq
|
|
40736
41811
|
};
|
|
40737
41812
|
}
|
|
41813
|
+
async appendConversationMessage(input) {
|
|
41814
|
+
const state = this.conversationStates.get(input.workspaceId);
|
|
41815
|
+
if (state?.status === "deleted")
|
|
41816
|
+
throw new Error("conversation.deleted");
|
|
41817
|
+
const rows = this.conversations.get(input.workspaceId) ?? [];
|
|
41818
|
+
const existing = rows.find((row) => row.messageId === input.messageId);
|
|
41819
|
+
if (existing)
|
|
41820
|
+
return { message: { ...existing }, created: false };
|
|
41821
|
+
const storedBytes = this.conversationBytes.get(input.workspaceId) ?? 0;
|
|
41822
|
+
const inputBytes = Buffer.byteLength(input.content) + Buffer.byteLength(JSON.stringify(input.metadata));
|
|
41823
|
+
if (rows.length >= MAX_CONVERSATION_MESSAGES2 || storedBytes + inputBytes > MAX_CONVERSATION_BYTES2) {
|
|
41824
|
+
throw new Error("conversation.quota_exceeded");
|
|
41825
|
+
}
|
|
41826
|
+
const message = { ...input, seq: (rows.at(-1)?.seq ?? 0) + 1 };
|
|
41827
|
+
rows.push(message);
|
|
41828
|
+
this.conversations.set(input.workspaceId, rows);
|
|
41829
|
+
this.conversationBytes.set(input.workspaceId, storedBytes + inputBytes);
|
|
41830
|
+
if (!state) {
|
|
41831
|
+
this.conversationStates.set(input.workspaceId, {
|
|
41832
|
+
workspaceId: input.workspaceId,
|
|
41833
|
+
status: "retained",
|
|
41834
|
+
expiresAt: null,
|
|
41835
|
+
deletedAt: null,
|
|
41836
|
+
updatedAt: input.createdAt
|
|
41837
|
+
});
|
|
41838
|
+
}
|
|
41839
|
+
return { message: { ...message }, created: true };
|
|
41840
|
+
}
|
|
41841
|
+
async readConversation(workspaceId, afterSeq, limit) {
|
|
41842
|
+
return (this.conversations.get(workspaceId) ?? []).filter((row) => row.seq > afterSeq).slice(0, limit).map((row) => ({ ...row, metadata: { ...row.metadata } }));
|
|
41843
|
+
}
|
|
41844
|
+
async getConversationState(workspaceId) {
|
|
41845
|
+
const row = this.conversationStates.get(workspaceId);
|
|
41846
|
+
return row ? { ...row } : null;
|
|
41847
|
+
}
|
|
41848
|
+
async setConversationExpiry(workspaceId, expiresAt, at) {
|
|
41849
|
+
const current = this.conversationStates.get(workspaceId);
|
|
41850
|
+
if (current?.status === "deleted")
|
|
41851
|
+
return;
|
|
41852
|
+
this.conversationStates.set(workspaceId, {
|
|
41853
|
+
workspaceId,
|
|
41854
|
+
status: "retained",
|
|
41855
|
+
expiresAt,
|
|
41856
|
+
deletedAt: null,
|
|
41857
|
+
updatedAt: at
|
|
41858
|
+
});
|
|
41859
|
+
}
|
|
41860
|
+
async deleteConversation(workspaceId, at) {
|
|
41861
|
+
this.conversations.delete(workspaceId);
|
|
41862
|
+
this.conversationBytes.delete(workspaceId);
|
|
41863
|
+
this.conversationStates.set(workspaceId, {
|
|
41864
|
+
workspaceId,
|
|
41865
|
+
status: "deleted",
|
|
41866
|
+
expiresAt: null,
|
|
41867
|
+
deletedAt: at,
|
|
41868
|
+
updatedAt: at
|
|
41869
|
+
});
|
|
41870
|
+
}
|
|
41871
|
+
async pruneExpiredConversations(at) {
|
|
41872
|
+
let deleted = 0;
|
|
41873
|
+
for (const state of this.conversationStates.values()) {
|
|
41874
|
+
if (state.status !== "retained" || !state.expiresAt || state.expiresAt > at)
|
|
41875
|
+
continue;
|
|
41876
|
+
if ((this.conversations.get(state.workspaceId)?.length ?? 0) > 0)
|
|
41877
|
+
deleted += 1;
|
|
41878
|
+
this.conversations.delete(state.workspaceId);
|
|
41879
|
+
this.conversationBytes.delete(state.workspaceId);
|
|
41880
|
+
}
|
|
41881
|
+
return deleted;
|
|
41882
|
+
}
|
|
40738
41883
|
async claimDueEvents(now, limit) {
|
|
40739
41884
|
const due = this.outbox.filter((e) => !e.deliveredAt && e.nextAttemptAt <= now && !this.claimedEvents.has(e.id)).sort((a, b) => a.occurredAt.getTime() - b.occurredAt.getTime()).slice(0, limit);
|
|
40740
41885
|
for (const e of due)
|
|
@@ -40760,7 +41905,7 @@ class MemoryStore {
|
|
|
40760
41905
|
}
|
|
40761
41906
|
async appendEvent(workspaceId, eventType, payload, at) {
|
|
40762
41907
|
this.outbox.push({
|
|
40763
|
-
id:
|
|
41908
|
+
id: randomUUID8(),
|
|
40764
41909
|
workspaceId,
|
|
40765
41910
|
eventType,
|
|
40766
41911
|
payload,
|
|
@@ -40772,6 +41917,9 @@ class MemoryStore {
|
|
|
40772
41917
|
});
|
|
40773
41918
|
}
|
|
40774
41919
|
}
|
|
41920
|
+
// ../../apps/pocketcoder-server/src/app.ts
|
|
41921
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
41922
|
+
|
|
40775
41923
|
// ../../node_modules/.bun/@asteasolutions+zod-to-openapi@8.5.0+68a1e3a0c4588df3/node_modules/@asteasolutions/zod-to-openapi/dist/index.mjs
|
|
40776
41924
|
function __rest(s, e) {
|
|
40777
41925
|
var t = {};
|
|
@@ -44393,7 +45541,7 @@ var createBunWebSocket = () => ({
|
|
|
44393
45541
|
});
|
|
44394
45542
|
|
|
44395
45543
|
// ../../apps/pocketcoder-server/src/hub.ts
|
|
44396
|
-
import { randomUUID as
|
|
45544
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
44397
45545
|
var MAX_INFLIGHT_RELAY = 16;
|
|
44398
45546
|
|
|
44399
45547
|
class Hub {
|
|
@@ -44502,12 +45650,12 @@ class Hub {
|
|
|
44502
45650
|
relay(workspaceId, request) {
|
|
44503
45651
|
const conn = this.byWorkspace.get(workspaceId);
|
|
44504
45652
|
if (!conn?.registered) {
|
|
44505
|
-
return Promise.resolve({ request_id:
|
|
45653
|
+
return Promise.resolve({ request_id: randomUUID9(), headers: {}, error_code: "unreachable" });
|
|
44506
45654
|
}
|
|
44507
45655
|
if (conn.inflight.size >= MAX_INFLIGHT_RELAY) {
|
|
44508
|
-
return Promise.resolve({ request_id:
|
|
45656
|
+
return Promise.resolve({ request_id: randomUUID9(), headers: {}, error_code: "deadline" });
|
|
44509
45657
|
}
|
|
44510
|
-
const requestId =
|
|
45658
|
+
const requestId = randomUUID9();
|
|
44511
45659
|
return new Promise((resolve7) => {
|
|
44512
45660
|
const timer = setTimeout(() => {
|
|
44513
45661
|
conn.inflight.delete(requestId);
|
|
@@ -44543,9 +45691,9 @@ class Hub {
|
|
|
44543
45691
|
}
|
|
44544
45692
|
|
|
44545
45693
|
// ../../apps/pocketcoder-server/src/middleware.ts
|
|
44546
|
-
import { randomUUID as
|
|
45694
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
44547
45695
|
var requestId = async (c, next) => {
|
|
44548
|
-
c.set("requestId",
|
|
45696
|
+
c.set("requestId", randomUUID10());
|
|
44549
45697
|
await next();
|
|
44550
45698
|
};
|
|
44551
45699
|
function machineAuth(store2, pepper) {
|
|
@@ -44583,14 +45731,100 @@ function requireScope(scope) {
|
|
|
44583
45731
|
};
|
|
44584
45732
|
}
|
|
44585
45733
|
function handleError(err, c) {
|
|
44586
|
-
const id = c.get("requestId") ??
|
|
45734
|
+
const id = c.get("requestId") ?? randomUUID10();
|
|
44587
45735
|
if (err instanceof ApiError) {
|
|
44588
|
-
return c.json(errorEnvelope(err.code, err.message, id), err.status);
|
|
45736
|
+
return c.json(errorEnvelope(err.code, err.message, id, err.details), err.status);
|
|
44589
45737
|
}
|
|
44590
45738
|
console.error(`[pocketcoder] request ${id} failed:`, err instanceof Error ? err.message : err);
|
|
44591
45739
|
return c.json(errorEnvelope("internal.error", "Internal error.", id), 500);
|
|
44592
45740
|
}
|
|
44593
45741
|
|
|
45742
|
+
// ../../apps/pocketcoder-server/src/pool-ws.ts
|
|
45743
|
+
class PoolConnectionHub {
|
|
45744
|
+
connections = new Map;
|
|
45745
|
+
attach(runtimeId, ws) {
|
|
45746
|
+
this.connections.get(runtimeId)?.close(1008, "superseded pool connection");
|
|
45747
|
+
this.connections.set(runtimeId, ws);
|
|
45748
|
+
}
|
|
45749
|
+
detach(runtimeId, ws) {
|
|
45750
|
+
if (this.connections.get(runtimeId) === ws)
|
|
45751
|
+
this.connections.delete(runtimeId);
|
|
45752
|
+
}
|
|
45753
|
+
assign(runtimeId, input) {
|
|
45754
|
+
const ws = this.connections.get(runtimeId);
|
|
45755
|
+
if (!ws)
|
|
45756
|
+
return false;
|
|
45757
|
+
const frame = LeaseAssignmentFrameSchema.parse({
|
|
45758
|
+
v: POOL_PROTOCOL_VERSION,
|
|
45759
|
+
type: "lease_assignment",
|
|
45760
|
+
input
|
|
45761
|
+
});
|
|
45762
|
+
try {
|
|
45763
|
+
ws.send(JSON.stringify(frame));
|
|
45764
|
+
return true;
|
|
45765
|
+
} catch {
|
|
45766
|
+
return false;
|
|
45767
|
+
}
|
|
45768
|
+
}
|
|
45769
|
+
isConnected(runtimeId) {
|
|
45770
|
+
return this.connections.has(runtimeId);
|
|
45771
|
+
}
|
|
45772
|
+
close(runtimeId) {
|
|
45773
|
+
const ws = this.connections.get(runtimeId);
|
|
45774
|
+
this.connections.delete(runtimeId);
|
|
45775
|
+
ws?.close(1000, "pool runtime drained");
|
|
45776
|
+
}
|
|
45777
|
+
}
|
|
45778
|
+
function poolConnectValidator(deps) {
|
|
45779
|
+
return async (c, next) => {
|
|
45780
|
+
const reject = (message) => c.json(errorEnvelope("auth.invalid_key", message, c.get("requestId") ?? ""), 401);
|
|
45781
|
+
if (Number(c.req.header(HEADER_PROTOCOL)) !== POOL_PROTOCOL_VERSION)
|
|
45782
|
+
return reject("Unsupported pool protocol version.");
|
|
45783
|
+
const runtimeId = c.req.header(HEADER_POOL_RUNTIME) ?? "";
|
|
45784
|
+
const secret = c.req.header(HEADER_POOL_ENROLLMENT) ?? "";
|
|
45785
|
+
const row = runtimeId ? await deps.store.getWarmPoolRuntime(runtimeId) : null;
|
|
45786
|
+
if (row?.state !== "provisioning" || !row.enrollmentDigest || !row.enrollmentExpiresAt || row.enrollmentExpiresAt <= new Date || !verifyOpaque(deps.pepper, secret, row.enrollmentDigest)) {
|
|
45787
|
+
return reject("Invalid or expired pool enrollment credential.");
|
|
45788
|
+
}
|
|
45789
|
+
c.set("poolAuth", { runtimeId });
|
|
45790
|
+
await next();
|
|
45791
|
+
};
|
|
45792
|
+
}
|
|
45793
|
+
function poolWsEvents(deps) {
|
|
45794
|
+
return (c) => {
|
|
45795
|
+
const auth = c.get("poolAuth");
|
|
45796
|
+
let registered = false;
|
|
45797
|
+
return {
|
|
45798
|
+
onMessage: (event, ws) => {
|
|
45799
|
+
(async () => {
|
|
45800
|
+
if (registered) {
|
|
45801
|
+
ws.close(1008, "pool runtime already registered");
|
|
45802
|
+
return;
|
|
45803
|
+
}
|
|
45804
|
+
const raw2 = typeof event.data === "string" ? event.data : Buffer.from(event.data).toString("utf8");
|
|
45805
|
+
const parsed = PoolRegisteredFrameSchema.safeParse(JSON.parse(raw2));
|
|
45806
|
+
const row = await deps.store.getWarmPoolRuntime(auth.runtimeId);
|
|
45807
|
+
if (!parsed.success || !row || parsed.data.pool_runtime_id !== row.id || parsed.data.template.digest !== row.templateDigest || parsed.data.template.name !== row.templateName || parsed.data.template.version !== row.templateVersion) {
|
|
45808
|
+
ws.close(1008, "invalid pool registration");
|
|
45809
|
+
return;
|
|
45810
|
+
}
|
|
45811
|
+
deps.hub.attach(row.id, ws);
|
|
45812
|
+
registered = await deps.manager.markReady(row.id);
|
|
45813
|
+
if (!registered)
|
|
45814
|
+
ws.close(1008, "pool registration no longer valid");
|
|
45815
|
+
})().catch((error51) => {
|
|
45816
|
+
deps.log?.(`pool ws ${auth.runtimeId}: ${String(error51)}`);
|
|
45817
|
+
ws.close(1011, "pool registration failed");
|
|
45818
|
+
});
|
|
45819
|
+
},
|
|
45820
|
+
onClose: (_event, ws) => {
|
|
45821
|
+
deps.hub.detach(auth.runtimeId, ws);
|
|
45822
|
+
deps.manager.disconnected(auth.runtimeId);
|
|
45823
|
+
}
|
|
45824
|
+
};
|
|
45825
|
+
};
|
|
45826
|
+
}
|
|
45827
|
+
|
|
44594
45828
|
// ../../apps/pocketcoder-server/src/relay.ts
|
|
44595
45829
|
var SAFE_RESPONSE_HEADERS = ["content-type"];
|
|
44596
45830
|
function requestPath(c, workspaceId, serviceName) {
|
|
@@ -44933,6 +46167,25 @@ async function handleConnectedFrame(deps, connection, ws, frame, closeProtocol)
|
|
|
44933
46167
|
return;
|
|
44934
46168
|
case "restore_status":
|
|
44935
46169
|
return;
|
|
46170
|
+
case "conversation_message": {
|
|
46171
|
+
const workspace2 = await deps.store.getWorkspace(frame.workspace_id);
|
|
46172
|
+
if (!workspace2 || isTerminal(workspace2.state))
|
|
46173
|
+
return;
|
|
46174
|
+
try {
|
|
46175
|
+
await deps.store.appendConversationMessage({
|
|
46176
|
+
workspaceId: frame.workspace_id,
|
|
46177
|
+
messageId: frame.payload.message_id,
|
|
46178
|
+
role: frame.payload.role,
|
|
46179
|
+
content: frame.payload.content,
|
|
46180
|
+
occurredAt: new Date(frame.payload.occurred_at),
|
|
46181
|
+
metadata: frame.payload.metadata,
|
|
46182
|
+
createdAt: new Date
|
|
46183
|
+
});
|
|
46184
|
+
} catch (error51) {
|
|
46185
|
+
deps.log?.(`conversation ${frame.workspace_id}: ${String(error51)}`);
|
|
46186
|
+
}
|
|
46187
|
+
return;
|
|
46188
|
+
}
|
|
44936
46189
|
}
|
|
44937
46190
|
}
|
|
44938
46191
|
function parseFrame(data, auth, connection, ws, closeProtocol) {
|
|
@@ -45026,6 +46279,35 @@ function safeTemplateItem(row) {
|
|
|
45026
46279
|
status: row.status
|
|
45027
46280
|
};
|
|
45028
46281
|
}
|
|
46282
|
+
var WorkspaceMetadataFilterSchema = exports_external.record(exports_external.string().min(1).max(64), exports_external.string().max(512)).refine((value) => Object.keys(value).length <= 16, "metadata filter has at most 16 keys");
|
|
46283
|
+
function parseMetadataFilter(value) {
|
|
46284
|
+
if (!value)
|
|
46285
|
+
return;
|
|
46286
|
+
try {
|
|
46287
|
+
const parsed = WorkspaceMetadataFilterSchema.safeParse(JSON.parse(value));
|
|
46288
|
+
if (parsed.success)
|
|
46289
|
+
return parsed.data;
|
|
46290
|
+
} catch {}
|
|
46291
|
+
throw new ApiError("validation.invalid", "metadata must be a JSON object containing bounded string keys and values.");
|
|
46292
|
+
}
|
|
46293
|
+
function workspaceListFilterOf(query) {
|
|
46294
|
+
const metadata = parseMetadataFilter(query.metadata);
|
|
46295
|
+
const createdAfter = query.created_after ? new Date(query.created_after) : undefined;
|
|
46296
|
+
const createdBefore = query.created_before ? new Date(query.created_before) : undefined;
|
|
46297
|
+
if (createdAfter && createdBefore && createdAfter >= createdBefore) {
|
|
46298
|
+
throw new ApiError("validation.invalid", "created_after must be before created_before.");
|
|
46299
|
+
}
|
|
46300
|
+
return {
|
|
46301
|
+
...query.external_id ? { externalId: query.external_id } : {},
|
|
46302
|
+
...query.state ? { state: query.state } : {},
|
|
46303
|
+
...query.template ? { template: query.template } : {},
|
|
46304
|
+
...metadata ? { metadata } : {},
|
|
46305
|
+
...createdAfter ? { createdAfter } : {},
|
|
46306
|
+
...createdBefore ? { createdBefore } : {},
|
|
46307
|
+
limit: query.limit,
|
|
46308
|
+
...query.cursor ? { cursor: query.cursor } : {}
|
|
46309
|
+
};
|
|
46310
|
+
}
|
|
45029
46311
|
var WorkspaceCreateHeadersSchema = exports_external.object({
|
|
45030
46312
|
"Idempotency-Key": exports_external.string().min(1).max(256).openapi({
|
|
45031
46313
|
description: "Opaque caller key. Reusing it with the same request returns the original workspace; a different request returns idempotency.conflict.",
|
|
@@ -45035,6 +46317,20 @@ var WorkspaceCreateHeadersSchema = exports_external.object({
|
|
|
45035
46317
|
function buildServer(deps) {
|
|
45036
46318
|
const { store: store2, driver: driver2, pepper, limits, log } = deps;
|
|
45037
46319
|
const hub = new Hub;
|
|
46320
|
+
const poolHub = new PoolConnectionHub;
|
|
46321
|
+
const secretFactory = {
|
|
46322
|
+
generate: generateOpaqueSecret,
|
|
46323
|
+
digest: (secret) => digestOpaque(pepper, secret)
|
|
46324
|
+
};
|
|
46325
|
+
const warmPool = deps.warmPools ? new WarmPoolManager({
|
|
46326
|
+
store: store2,
|
|
46327
|
+
driver: driver2,
|
|
46328
|
+
connections: poolHub,
|
|
46329
|
+
secrets: secretFactory,
|
|
46330
|
+
workspaceServerUrl: deps.workspaceServerUrl,
|
|
46331
|
+
pools: deps.warmPools,
|
|
46332
|
+
onError: (context, error51) => log?.(`${context}: ${String(error51)}`)
|
|
46333
|
+
}) : undefined;
|
|
45038
46334
|
const persistenceHolder = {};
|
|
45039
46335
|
const scheduler2 = new Scheduler({
|
|
45040
46336
|
store: store2,
|
|
@@ -45042,12 +46338,10 @@ function buildServer(deps) {
|
|
|
45042
46338
|
...deps.storageDriver ? { storageDriver: deps.storageDriver } : {},
|
|
45043
46339
|
...deps.secretResolver ? { secretResolver: deps.secretResolver } : {},
|
|
45044
46340
|
connections: hub,
|
|
45045
|
-
secrets:
|
|
45046
|
-
generate: generateOpaqueSecret,
|
|
45047
|
-
digest: (secret) => digestOpaque(pepper, secret)
|
|
45048
|
-
},
|
|
46341
|
+
secrets: secretFactory,
|
|
45049
46342
|
limits,
|
|
45050
46343
|
workspaceServerUrl: deps.workspaceServerUrl,
|
|
46344
|
+
...warmPool ? { warmPool } : {},
|
|
45051
46345
|
preserveByPolicy: async (row, trigger) => persistenceHolder.service?.preserveByPolicy(row, trigger) ?? false,
|
|
45052
46346
|
onError: (context, err) => log?.(`scheduler ${context}: ${String(err)}`)
|
|
45053
46347
|
});
|
|
@@ -45091,6 +46385,9 @@ function buildServer(deps) {
|
|
|
45091
46385
|
persistence: persistence2
|
|
45092
46386
|
};
|
|
45093
46387
|
app.get("/v1/agent/connect", agentConnectValidator(wsDeps), upgradeWebSocket2(agentWsEvents(wsDeps)));
|
|
46388
|
+
if (warmPool) {
|
|
46389
|
+
app.get("/v1/agent/pool-connect", poolConnectValidator({ store: store2, pepper }), upgradeWebSocket2(poolWsEvents({ store: store2, hub: poolHub, manager: warmPool, ...log ? { log } : {} })));
|
|
46390
|
+
}
|
|
45094
46391
|
app.doc("/v1/openapi.json", {
|
|
45095
46392
|
openapi: "3.0.0",
|
|
45096
46393
|
info: {
|
|
@@ -45120,6 +46417,26 @@ function buildServer(deps) {
|
|
|
45120
46417
|
const rows = await store2.listTemplates(names);
|
|
45121
46418
|
return c.json({ items: rows.map(safeTemplateItem) }, 200);
|
|
45122
46419
|
});
|
|
46420
|
+
if (warmPool) {
|
|
46421
|
+
app.openapi(createRoute({
|
|
46422
|
+
method: "get",
|
|
46423
|
+
path: "/v1/warm-pools",
|
|
46424
|
+
middleware: [requireScope("admin")],
|
|
46425
|
+
responses: {
|
|
46426
|
+
200: {
|
|
46427
|
+
description: "Warm runtime inventory and cumulative metrics",
|
|
46428
|
+
content: {
|
|
46429
|
+
"application/json": {
|
|
46430
|
+
schema: exports_external.object({
|
|
46431
|
+
items: exports_external.array(exports_external.record(exports_external.string(), exports_external.unknown())),
|
|
46432
|
+
metrics: exports_external.record(exports_external.string(), exports_external.number())
|
|
46433
|
+
})
|
|
46434
|
+
}
|
|
46435
|
+
}
|
|
46436
|
+
}
|
|
46437
|
+
}
|
|
46438
|
+
}), async (c) => c.json(await warmPool.inventory(), 200));
|
|
46439
|
+
}
|
|
45123
46440
|
app.openapi(createRoute({
|
|
45124
46441
|
method: "get",
|
|
45125
46442
|
path: "/v1/storage/inventory",
|
|
@@ -45152,13 +46469,21 @@ function buildServer(deps) {
|
|
|
45152
46469
|
"application/json": {
|
|
45153
46470
|
schema: exports_external.object({
|
|
45154
46471
|
deleted: exports_external.number(),
|
|
45155
|
-
skipped: exports_external.number()
|
|
46472
|
+
skipped: exports_external.number(),
|
|
46473
|
+
transcripts_deleted: exports_external.number()
|
|
45156
46474
|
})
|
|
45157
46475
|
}
|
|
45158
46476
|
}
|
|
45159
46477
|
}
|
|
45160
46478
|
}
|
|
45161
|
-
}), async (c) =>
|
|
46479
|
+
}), async (c) => {
|
|
46480
|
+
const result = await persistence2.pruneExpired();
|
|
46481
|
+
return c.json({
|
|
46482
|
+
deleted: result.deleted,
|
|
46483
|
+
skipped: result.skipped,
|
|
46484
|
+
transcripts_deleted: result.transcriptsDeleted
|
|
46485
|
+
}, 200);
|
|
46486
|
+
});
|
|
45162
46487
|
app.openapi(createRoute({
|
|
45163
46488
|
method: "post",
|
|
45164
46489
|
path: "/v1/workspaces/{id}/preserve",
|
|
@@ -45366,6 +46691,131 @@ function buildServer(deps) {
|
|
|
45366
46691
|
operation: toOperationResource(result.operation)
|
|
45367
46692
|
}, 202);
|
|
45368
46693
|
});
|
|
46694
|
+
app.openapi(createRoute({
|
|
46695
|
+
method: "post",
|
|
46696
|
+
path: "/v1/workspaces/{id}/resume",
|
|
46697
|
+
middleware: [requireScope("workspaces:restore")],
|
|
46698
|
+
request: {
|
|
46699
|
+
params: exports_external.object({ id: exports_external.uuid() }),
|
|
46700
|
+
headers: exports_external.object({ "idempotency-key": exports_external.string().min(1).max(256) }),
|
|
46701
|
+
body: { content: { "application/json": { schema: RestoreRequestSchema } } }
|
|
46702
|
+
},
|
|
46703
|
+
responses: {
|
|
46704
|
+
202: {
|
|
46705
|
+
description: "New workspace queued with supported conversation context",
|
|
46706
|
+
content: {
|
|
46707
|
+
"application/json": {
|
|
46708
|
+
schema: exports_external.object({
|
|
46709
|
+
workspace: WorkspaceResourceSchema,
|
|
46710
|
+
operation: OperationResourceSchema,
|
|
46711
|
+
resume: ConversationResumeOutcomeSchema
|
|
46712
|
+
})
|
|
46713
|
+
}
|
|
46714
|
+
}
|
|
46715
|
+
}
|
|
46716
|
+
}
|
|
46717
|
+
}), async (c) => {
|
|
46718
|
+
const principal = c.get("principal");
|
|
46719
|
+
const source = await service.getOwned(principal, c.req.valid("param").id);
|
|
46720
|
+
const checkpoints = await store2.listCheckpoints(principal.id, {
|
|
46721
|
+
workspaceId: source.id,
|
|
46722
|
+
state: "ready"
|
|
46723
|
+
});
|
|
46724
|
+
const checkpoint = checkpoints[0];
|
|
46725
|
+
if (!checkpoint) {
|
|
46726
|
+
throw new ApiError("checkpoint.none_ready", "Workspace has no ready checkpoint.");
|
|
46727
|
+
}
|
|
46728
|
+
if (checkpoint.conversationRestore !== "supported") {
|
|
46729
|
+
const reason = checkpoint.conversationRestore === "filesystem_only" ? "filesystem_only" : "capability_unknown";
|
|
46730
|
+
throw new ApiError("resume.unsupported", "This checkpoint cannot restore conversation context.", { reason, checkpoint_id: checkpoint.id });
|
|
46731
|
+
}
|
|
46732
|
+
const result = await persistence2.restore(principal, checkpoint.id, c.req.valid("json"), c.req.valid("header")["idempotency-key"]);
|
|
46733
|
+
const workspace2 = await service.getOwned(principal, result.workspaceId);
|
|
46734
|
+
return c.json({
|
|
46735
|
+
workspace: toResource(workspace2),
|
|
46736
|
+
operation: toOperationResource(result.operation),
|
|
46737
|
+
resume: {
|
|
46738
|
+
status: "supported",
|
|
46739
|
+
reason: null,
|
|
46740
|
+
source_workspace_id: source.id,
|
|
46741
|
+
checkpoint_id: checkpoint.id
|
|
46742
|
+
}
|
|
46743
|
+
}, 202);
|
|
46744
|
+
});
|
|
46745
|
+
app.openapi(createRoute({
|
|
46746
|
+
method: "get",
|
|
46747
|
+
path: "/v1/workspaces/{id}/conversation",
|
|
46748
|
+
middleware: [requireScope("conversations:read")],
|
|
46749
|
+
request: {
|
|
46750
|
+
params: exports_external.object({ id: exports_external.uuid() }),
|
|
46751
|
+
query: ConversationListQuerySchema
|
|
46752
|
+
},
|
|
46753
|
+
responses: {
|
|
46754
|
+
200: {
|
|
46755
|
+
description: "Durable conversation transcript for an active or terminal workspace",
|
|
46756
|
+
content: {
|
|
46757
|
+
"application/json": {
|
|
46758
|
+
schema: exports_external.object({
|
|
46759
|
+
items: exports_external.array(ConversationMessageResourceSchema),
|
|
46760
|
+
next_cursor: exports_external.number().int().positive().nullable(),
|
|
46761
|
+
retention: exports_external.object({
|
|
46762
|
+
status: exports_external.literal("retained"),
|
|
46763
|
+
expires_at: exports_external.iso.datetime().nullable()
|
|
46764
|
+
})
|
|
46765
|
+
})
|
|
46766
|
+
}
|
|
46767
|
+
}
|
|
46768
|
+
}
|
|
46769
|
+
}
|
|
46770
|
+
}), async (c) => {
|
|
46771
|
+
const principal = c.get("principal");
|
|
46772
|
+
const workspace2 = await service.getOwned(principal, c.req.valid("param").id);
|
|
46773
|
+
const state = await store2.getConversationState(workspace2.id);
|
|
46774
|
+
if (state?.status === "deleted") {
|
|
46775
|
+
throw new ApiError("conversation.deleted", "Conversation history was deleted.");
|
|
46776
|
+
}
|
|
46777
|
+
const expiresAt = state?.expiresAt ?? (workspace2.terminalAt ? new Date(workspace2.terminalAt.getTime() + parseDurationMs(workspace2.templateSnapshot.spec.persistence.conversationRetention)) : null);
|
|
46778
|
+
if (expiresAt && expiresAt <= new Date) {
|
|
46779
|
+
throw new ApiError("conversation.expired", "Conversation history has expired.");
|
|
46780
|
+
}
|
|
46781
|
+
const { after, limit } = c.req.valid("query");
|
|
46782
|
+
const rows = await store2.readConversation(workspace2.id, after, limit);
|
|
46783
|
+
const last = rows.at(-1);
|
|
46784
|
+
return c.json({
|
|
46785
|
+
items: rows.map((row) => ({
|
|
46786
|
+
message_id: row.messageId,
|
|
46787
|
+
seq: row.seq,
|
|
46788
|
+
role: row.role,
|
|
46789
|
+
content: row.content,
|
|
46790
|
+
occurred_at: row.occurredAt.toISOString(),
|
|
46791
|
+
metadata: row.metadata
|
|
46792
|
+
})),
|
|
46793
|
+
next_cursor: rows.length === limit && last ? last.seq : null,
|
|
46794
|
+
retention: { status: "retained", expires_at: expiresAt?.toISOString() ?? null }
|
|
46795
|
+
}, 200);
|
|
46796
|
+
});
|
|
46797
|
+
app.openapi(createRoute({
|
|
46798
|
+
method: "delete",
|
|
46799
|
+
path: "/v1/workspaces/{id}/conversation",
|
|
46800
|
+
middleware: [requireScope("conversations:delete")],
|
|
46801
|
+
request: { params: exports_external.object({ id: exports_external.uuid() }) },
|
|
46802
|
+
responses: { 204: { description: "Conversation content deleted idempotently" } }
|
|
46803
|
+
}), async (c) => {
|
|
46804
|
+
const principal = c.get("principal");
|
|
46805
|
+
const workspace2 = await service.getOwned(principal, c.req.valid("param").id);
|
|
46806
|
+
const at = new Date;
|
|
46807
|
+
const previous = await store2.getConversationState(workspace2.id);
|
|
46808
|
+
await store2.deleteConversation(workspace2.id, at);
|
|
46809
|
+
if (previous?.status !== "deleted") {
|
|
46810
|
+
await store2.appendEvent(workspace2.id, "workspace.conversation_deleted", {
|
|
46811
|
+
id: randomUUID11(),
|
|
46812
|
+
type: "workspace.conversation_deleted",
|
|
46813
|
+
occurred_at: at.toISOString(),
|
|
46814
|
+
workspace_id: workspace2.id
|
|
46815
|
+
}, at);
|
|
46816
|
+
}
|
|
46817
|
+
return c.body(null, 204);
|
|
46818
|
+
});
|
|
45369
46819
|
app.openapi(createRoute({
|
|
45370
46820
|
method: "get",
|
|
45371
46821
|
path: "/v1/workspaces/{id}/outputs",
|
|
@@ -45480,13 +46930,7 @@ function buildServer(deps) {
|
|
|
45480
46930
|
}), async (c) => {
|
|
45481
46931
|
const principal = c.get("principal");
|
|
45482
46932
|
const query = c.req.valid("query");
|
|
45483
|
-
const rows = await store2.listWorkspaces(principal.id,
|
|
45484
|
-
...query.external_id ? { externalId: query.external_id } : {},
|
|
45485
|
-
...query.state ? { state: query.state } : {},
|
|
45486
|
-
...query.template ? { template: query.template } : {},
|
|
45487
|
-
limit: query.limit,
|
|
45488
|
-
...query.cursor ? { cursor: query.cursor } : {}
|
|
45489
|
-
});
|
|
46933
|
+
const rows = await store2.listWorkspaces(principal.id, workspaceListFilterOf(query));
|
|
45490
46934
|
const last = rows[rows.length - 1];
|
|
45491
46935
|
return c.json({
|
|
45492
46936
|
items: rows.map(toResource),
|
|
@@ -45609,7 +47053,15 @@ function buildServer(deps) {
|
|
|
45609
47053
|
}, 200);
|
|
45610
47054
|
});
|
|
45611
47055
|
app.on(["GET", "POST", "PUT", "PATCH", "DELETE"], "/v1/workspaces/:id/services/:service/*", requireScope("services:relay"), relayHandler({ store: store2, hub, service }));
|
|
45612
|
-
return {
|
|
47056
|
+
return {
|
|
47057
|
+
app,
|
|
47058
|
+
websocket: websocket2,
|
|
47059
|
+
hub,
|
|
47060
|
+
scheduler: scheduler2,
|
|
47061
|
+
service,
|
|
47062
|
+
persistence: persistence2,
|
|
47063
|
+
...warmPool ? { warmPool } : {}
|
|
47064
|
+
};
|
|
45613
47065
|
}
|
|
45614
47066
|
|
|
45615
47067
|
// ../../apps/pocketcoder-server/src/lifecycle.ts
|
|
@@ -45698,9 +47150,11 @@ async function startPocketcoderServer(config2 = loadConfig(), options = {}) {
|
|
|
45698
47150
|
try {
|
|
45699
47151
|
await loadConfiguredTemplates(store2, config2.templateDir, log);
|
|
45700
47152
|
const driver2 = createWorkspaceDriver(config2);
|
|
47153
|
+
const warmPools = await resolveWarmPools(store2, config2.warmPools, driver2.kind, config2.limits.globalActiveWorkspaces);
|
|
47154
|
+
const warmPoolContinuously = warmPools.length > 0 || (await store2.listWarmPoolRuntimes()).some((runtime) => runtime.state !== "failed");
|
|
45701
47155
|
const storageDriver = createStorageDriver(config2);
|
|
45702
47156
|
const secretResolver = createSecretResolver(config2);
|
|
45703
|
-
const { app, websocket: websocket2, scheduler: scheduler2, persistence: persistence2 } = buildServer({
|
|
47157
|
+
const { app, websocket: websocket2, scheduler: scheduler2, persistence: persistence2, warmPool } = buildServer({
|
|
45704
47158
|
store: store2,
|
|
45705
47159
|
driver: driver2,
|
|
45706
47160
|
...storageDriver ? { storageDriver } : {},
|
|
@@ -45710,7 +47164,8 @@ async function startPocketcoderServer(config2 = loadConfig(), options = {}) {
|
|
|
45710
47164
|
workspaceServerUrl: config2.workspaceServerUrl,
|
|
45711
47165
|
persistenceLimits: config2.persistenceLimits,
|
|
45712
47166
|
...options.instanceId ? { instanceId: options.instanceId } : {},
|
|
45713
|
-
log
|
|
47167
|
+
log,
|
|
47168
|
+
warmPools
|
|
45714
47169
|
});
|
|
45715
47170
|
await reconcileStartup(store2, driver2, storageDriver, log);
|
|
45716
47171
|
const outbox2 = new OutboxDispatcher({
|
|
@@ -45721,6 +47176,7 @@ async function startPocketcoderServer(config2 = loadConfig(), options = {}) {
|
|
|
45721
47176
|
});
|
|
45722
47177
|
const schedulerTimer = startExclusiveTimer(config2.schedulerIntervalMs, () => scheduler2.tick(), "scheduler tick failed", log);
|
|
45723
47178
|
const outboxTimer = startExclusiveTimer(config2.outboxIntervalMs, () => outbox2.tick(), "outbox tick failed", log);
|
|
47179
|
+
const warmPoolTimer = warmPool && warmPoolContinuously ? startExclusiveTimer(config2.schedulerIntervalMs, () => warmPool.reconcile(), "warm pool reconciliation failed", log) : null;
|
|
45724
47180
|
const retentionTimer = startExclusiveTimer(60000, async () => {
|
|
45725
47181
|
const { deleted, skipped } = await persistence2.pruneExpired();
|
|
45726
47182
|
if (deleted > 0 || skipped > 0) {
|
|
@@ -45739,12 +47195,16 @@ async function startPocketcoderServer(config2 = loadConfig(), options = {}) {
|
|
|
45739
47195
|
clearInterval(schedulerTimer);
|
|
45740
47196
|
clearInterval(outboxTimer);
|
|
45741
47197
|
clearInterval(retentionTimer);
|
|
47198
|
+
if (warmPoolTimer)
|
|
47199
|
+
clearInterval(warmPoolTimer);
|
|
45742
47200
|
throw error51;
|
|
45743
47201
|
}
|
|
45744
47202
|
const healthHost = config2.listenHost === "0.0.0.0" || config2.listenHost === "::" ? "127.0.0.1" : config2.listenHost;
|
|
45745
47203
|
const url2 = `http://${healthHost}:${server.port}`;
|
|
45746
47204
|
log(`listening on http://${config2.listenHost}:${server.port}`);
|
|
45747
47205
|
log(`workspaces reach this server at ${config2.workspaceServerUrl}`);
|
|
47206
|
+
if (warmPool)
|
|
47207
|
+
warmPool.reconcile().catch((error51) => log(`warm pool initial reconcile failed: ${String(error51)}`));
|
|
45748
47208
|
let stopPromise = null;
|
|
45749
47209
|
return {
|
|
45750
47210
|
config: config2,
|
|
@@ -45757,6 +47217,8 @@ async function startPocketcoderServer(config2 = loadConfig(), options = {}) {
|
|
|
45757
47217
|
clearInterval(schedulerTimer);
|
|
45758
47218
|
clearInterval(outboxTimer);
|
|
45759
47219
|
clearInterval(retentionTimer);
|
|
47220
|
+
if (warmPoolTimer)
|
|
47221
|
+
clearInterval(warmPoolTimer);
|
|
45760
47222
|
await server.stop(true);
|
|
45761
47223
|
await store2.close();
|
|
45762
47224
|
})();
|
|
@@ -45812,7 +47274,7 @@ function readState() {
|
|
|
45812
47274
|
function writeState(state) {
|
|
45813
47275
|
const path = statePath();
|
|
45814
47276
|
mkdirSync(dirname5(path), { recursive: true, mode: 448 });
|
|
45815
|
-
const temporary = `${path}.${process.pid}.${
|
|
47277
|
+
const temporary = `${path}.${process.pid}.${randomUUID12()}.tmp`;
|
|
45816
47278
|
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}
|
|
45817
47279
|
`, { mode: 384 });
|
|
45818
47280
|
renameSync(temporary, path);
|
|
@@ -45921,7 +47383,7 @@ async function startManagedServer(options) {
|
|
|
45921
47383
|
if (existing)
|
|
45922
47384
|
removeState();
|
|
45923
47385
|
const config2 = loadConfig();
|
|
45924
|
-
const instanceToken =
|
|
47386
|
+
const instanceToken = randomUUID12();
|
|
45925
47387
|
const state = {
|
|
45926
47388
|
version: 1,
|
|
45927
47389
|
pid: 0,
|
|
@@ -46311,7 +47773,7 @@ async function chatWorkspace(flags, deps) {
|
|
|
46311
47773
|
}
|
|
46312
47774
|
|
|
46313
47775
|
// src/workspace-create.ts
|
|
46314
|
-
import { randomUUID as
|
|
47776
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
46315
47777
|
var TERMINAL_WORKSPACE_STATES = new Set([
|
|
46316
47778
|
"succeeded",
|
|
46317
47779
|
"failed",
|
|
@@ -46401,7 +47863,7 @@ async function waitForReady(initial, flags, deps) {
|
|
|
46401
47863
|
}
|
|
46402
47864
|
async function createWorkspace(flags, deps) {
|
|
46403
47865
|
const template2 = need2(flags, "template", deps.fail);
|
|
46404
|
-
const externalId = typeof flags["external-id"] === "string" ? flags["external-id"] : `pcd-${
|
|
47866
|
+
const externalId = typeof flags["external-id"] === "string" ? flags["external-id"] : `pcd-${randomUUID13()}`;
|
|
46405
47867
|
const launchInput = parseLaunchInput(flags, deps.fail);
|
|
46406
47868
|
const response = await deps.api("/v1/workspaces", {
|
|
46407
47869
|
method: "POST",
|
|
@@ -46601,6 +48063,7 @@ function createCli(argv) {
|
|
|
46601
48063
|
array: true,
|
|
46602
48064
|
description: "Template manifest files"
|
|
46603
48065
|
})).command("list", "List authorized template versions through the REST API", (command2) => command2.option("json", { type: "boolean", description: "Print JSON" })).command("list-database", "List every template version from PostgreSQL"));
|
|
48066
|
+
parser2 = commandGroup(parser2, "pools", "Inspect operator-managed warm capacity", (group) => group.command("list", "List warm pool inventory and metrics", (command2) => command2.option("json", { type: "boolean", description: "Print JSON" })));
|
|
46604
48067
|
parser2 = commandGroup(parser2, "workspaces", "Manage workspaces", (group) => group.command("list", "List workspaces", (command2) => command2.option("active", {
|
|
46605
48068
|
type: "boolean",
|
|
46606
48069
|
description: "Only show nonterminal workspaces"
|
|
@@ -46885,6 +48348,25 @@ async function handleTemplates(context) {
|
|
|
46885
48348
|
return false;
|
|
46886
48349
|
}
|
|
46887
48350
|
}
|
|
48351
|
+
async function handlePools(context) {
|
|
48352
|
+
if (context.group !== "pools" || context.action !== "list")
|
|
48353
|
+
return false;
|
|
48354
|
+
const response = await api2("/v1/warm-pools");
|
|
48355
|
+
const body = await response.json();
|
|
48356
|
+
if (!response.ok)
|
|
48357
|
+
fail(`warm pool inventory failed (${response.status}): ${JSON.stringify(body)}`);
|
|
48358
|
+
if (context.flags.json) {
|
|
48359
|
+
console.log(JSON.stringify(body, null, 2));
|
|
48360
|
+
return true;
|
|
48361
|
+
}
|
|
48362
|
+
for (const item of body.items ?? []) {
|
|
48363
|
+
const counts = Object.entries(item.counts).map(([state, count3]) => `${state}=${count3}`).join(" ");
|
|
48364
|
+
console.log(`${item.template}@${item.version} desired=${item.desired} ${counts || "empty"} oldest_ready_ms=${item.oldest_ready_age_ms ?? "-"}`);
|
|
48365
|
+
}
|
|
48366
|
+
if (body.metrics)
|
|
48367
|
+
console.log(`metrics ${Object.entries(body.metrics).map(([name2, value]) => `${name2}=${value}`).join(" ")}`);
|
|
48368
|
+
return true;
|
|
48369
|
+
}
|
|
46888
48370
|
async function listWorkspaces(flags) {
|
|
46889
48371
|
const params = new URLSearchParams;
|
|
46890
48372
|
for (const [flag, param2] of [
|
|
@@ -46968,7 +48450,7 @@ async function handleWorkspacePersistence({
|
|
|
46968
48450
|
};
|
|
46969
48451
|
const res = await api2(`/v1/workspaces/${id}/preserve`, {
|
|
46970
48452
|
method: "POST",
|
|
46971
|
-
headers: { "idempotency-key": `preserve-${id}-${
|
|
48453
|
+
headers: { "idempotency-key": `preserve-${id}-${randomUUID14()}` },
|
|
46972
48454
|
body: JSON.stringify(body)
|
|
46973
48455
|
});
|
|
46974
48456
|
console.log(JSON.stringify(await res.json(), null, 2));
|
|
@@ -47045,7 +48527,7 @@ async function verifyCheckpoint(flags) {
|
|
|
47045
48527
|
const id = need3(flags, "id");
|
|
47046
48528
|
const response = await api2(`/v1/checkpoints/${id}/verify`, {
|
|
47047
48529
|
method: "POST",
|
|
47048
|
-
headers: { "idempotency-key": `verify-${id}-${
|
|
48530
|
+
headers: { "idempotency-key": `verify-${id}-${randomUUID14()}` }
|
|
47049
48531
|
});
|
|
47050
48532
|
console.log(JSON.stringify(await response.json(), null, 2));
|
|
47051
48533
|
if (!response.ok)
|
|
@@ -47156,7 +48638,7 @@ function containsDoctorNonce(message, nonce) {
|
|
|
47156
48638
|
return !["user", "human"].includes(role) && JSON.stringify(message).includes(nonce);
|
|
47157
48639
|
}
|
|
47158
48640
|
async function runDoctorTurn(workspaceId, timeoutSeconds2) {
|
|
47159
|
-
const nonce = `pocketcoder-doctor-${
|
|
48641
|
+
const nonce = `pocketcoder-doctor-${randomUUID14()}`;
|
|
47160
48642
|
console.log("doctor: sending a correlated request/response probe");
|
|
47161
48643
|
const messageResponse = await api2(`/v1/workspaces/${workspaceId}/services/agent/message`, {
|
|
47162
48644
|
method: "POST",
|
|
@@ -47202,7 +48684,7 @@ async function handleDoctor({ group, flags }) {
|
|
|
47202
48684
|
if (group !== "doctor")
|
|
47203
48685
|
return false;
|
|
47204
48686
|
const template2 = need3(flags, "template");
|
|
47205
|
-
const externalId = `doctor-${
|
|
48687
|
+
const externalId = `doctor-${randomUUID14()}`;
|
|
47206
48688
|
const turnTimeoutSeconds = doctorTurnTimeout(flags);
|
|
47207
48689
|
console.log(`doctor: creating probe workspace from template ${template2}`);
|
|
47208
48690
|
const createRes = await api2("/v1/workspaces", {
|
|
@@ -47244,6 +48726,7 @@ async function dispatchCommand(context) {
|
|
|
47244
48726
|
handlePrincipals,
|
|
47245
48727
|
handleKeys,
|
|
47246
48728
|
handleTemplates,
|
|
48729
|
+
handlePools,
|
|
47247
48730
|
handleWorkspaceCore,
|
|
47248
48731
|
handleWorkspacePersistence,
|
|
47249
48732
|
handleWorkspaceAttach,
|