devchain-cli 0.19.0 → 0.20.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/README.md +0 -1
- package/dist/drizzle/0069_fluffy_exodus.sql +2 -0
- package/dist/drizzle/0070_lyrical_tombstone.sql +1 -0
- package/dist/drizzle/0071_special_avengers.sql +22 -0
- package/dist/drizzle/meta/0069_snapshot.json +5767 -0
- package/dist/drizzle/meta/0070_snapshot.json +5774 -0
- package/dist/drizzle/meta/0071_snapshot.json +5931 -0
- package/dist/drizzle/meta/_journal.json +22 -1
- package/dist/node_modules/@devchain/shared/e2ee/base64.js +1 -1
- package/dist/node_modules/@devchain/shared/e2ee/base64.js.map +1 -1
- package/dist/node_modules/@devchain/shared/host-resolver.d.ts.map +1 -1
- package/dist/node_modules/@devchain/shared/host-resolver.js.map +1 -1
- package/dist/node_modules/@devchain/shared/schemas/claude-launch-settings.d.ts.map +1 -1
- package/dist/node_modules/@devchain/shared/schemas/claude-launch-settings.js +23 -0
- package/dist/node_modules/@devchain/shared/schemas/claude-launch-settings.js.map +1 -1
- package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts +363 -6
- package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts.map +1 -1
- package/dist/node_modules/@devchain/shared/schemas/export-schema.js +11 -1
- package/dist/node_modules/@devchain/shared/schemas/export-schema.js.map +1 -1
- package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
- package/dist/server/common/config/container-scope.d.ts +4 -0
- package/dist/server/common/config/container-scope.js +11 -0
- package/dist/server/common/config/container-scope.js.map +1 -0
- package/dist/server/modules/agent-message-delivery/adapters/legacy-delivery-formatter.adapter.js +12 -0
- package/dist/server/modules/agent-message-delivery/adapters/legacy-delivery-formatter.adapter.js.map +1 -1
- package/dist/server/modules/agent-message-delivery/agent-message-delivery.service.d.ts +1 -0
- package/dist/server/modules/agent-message-delivery/agent-message-delivery.service.js +18 -4
- package/dist/server/modules/agent-message-delivery/agent-message-delivery.service.js.map +1 -1
- package/dist/server/modules/agent-message-delivery/dtos/delivery.types.d.ts +16 -3
- package/dist/server/modules/agents/controllers/agents.controller.d.ts +1 -0
- package/dist/server/modules/agents/controllers/agents.controller.js +3 -0
- package/dist/server/modules/agents/controllers/agents.controller.js.map +1 -1
- package/dist/server/modules/epics/services/epics.service.d.ts +2 -0
- package/dist/server/modules/epics/services/epics.service.js +17 -3
- package/dist/server/modules/epics/services/epics.service.js.map +1 -1
- package/dist/server/modules/events/catalog/agent.message.sent.d.ts +89 -0
- package/dist/server/modules/events/catalog/agent.message.sent.js +16 -0
- package/dist/server/modules/events/catalog/agent.message.sent.js.map +1 -1
- package/dist/server/modules/events/catalog/broadcast-registry.js +1 -0
- package/dist/server/modules/events/catalog/broadcast-registry.js.map +1 -1
- package/dist/server/modules/events/catalog/index.d.ts +97 -6
- package/dist/server/modules/events/catalog/index.js +9 -1
- package/dist/server/modules/events/catalog/index.js.map +1 -1
- package/dist/server/modules/events/catalog/review.created.d.ts +4 -4
- package/dist/server/modules/events/catalog/session.recommendation.d.ts +2 -2
- package/dist/server/modules/events/services/event-log.service.d.ts +3 -3
- package/dist/server/modules/events/services/event-log.service.js +58 -26
- package/dist/server/modules/events/services/event-log.service.js.map +1 -1
- package/dist/server/modules/events/services/events.service.d.ts +1 -1
- package/dist/server/modules/events/services/events.service.js +5 -0
- package/dist/server/modules/events/services/events.service.js.map +1 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +45 -0
- package/dist/server/modules/mcp/dtos/mcp.dto.js +29 -2
- package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
- package/dist/server/modules/mcp/mcp-full.module.js +2 -0
- package/dist/server/modules/mcp/mcp-full.module.js.map +1 -1
- package/dist/server/modules/mcp/services/handlers/chat-context.d.ts +2 -0
- package/dist/server/modules/mcp/services/handlers/chat-tools.js +10 -0
- package/dist/server/modules/mcp/services/handlers/chat-tools.js.map +1 -1
- package/dist/server/modules/mcp/services/handlers/epic-tools.js +7 -3
- package/dist/server/modules/mcp/services/handlers/epic-tools.js.map +1 -1
- package/dist/server/modules/mcp/services/handlers/project-context.d.ts +6 -0
- package/dist/server/modules/mcp/services/handlers/project-context.js +3 -0
- package/dist/server/modules/mcp/services/handlers/project-context.js.map +1 -0
- package/dist/server/modules/mcp/services/handlers/project-tools.d.ts +3 -0
- package/dist/server/modules/mcp/services/handlers/project-tools.js +32 -0
- package/dist/server/modules/mcp/services/handlers/project-tools.js.map +1 -0
- package/dist/server/modules/mcp/services/mappers/dto-mappers.js +1 -0
- package/dist/server/modules/mcp/services/mappers/dto-mappers.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp.service.d.ts +4 -1
- package/dist/server/modules/mcp/services/mcp.service.js +46 -29
- package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
- package/dist/server/modules/mcp/tool-descriptors/chat.metadata.js +6 -1
- package/dist/server/modules/mcp/tool-descriptors/chat.metadata.js.map +1 -1
- package/dist/server/modules/mcp/tool-descriptors/index.js +4 -0
- package/dist/server/modules/mcp/tool-descriptors/index.js.map +1 -1
- package/dist/server/modules/mcp/tool-descriptors/project.bindings.d.ts +2 -0
- package/dist/server/modules/mcp/tool-descriptors/project.bindings.js +8 -0
- package/dist/server/modules/mcp/tool-descriptors/project.bindings.js.map +1 -0
- package/dist/server/modules/mcp/tool-descriptors/project.metadata.d.ts +2 -0
- package/dist/server/modules/mcp/tool-descriptors/project.metadata.js +37 -0
- package/dist/server/modules/mcp/tool-descriptors/project.metadata.js.map +1 -0
- package/dist/server/modules/orchestrator/worktrees/services/worktrees.service.js +0 -2
- package/dist/server/modules/orchestrator/worktrees/services/worktrees.service.js.map +1 -1
- package/dist/server/modules/profiles/dto.d.ts +4 -4
- package/dist/server/modules/project-communication/dtos/project-communication.types.d.ts +44 -0
- package/dist/server/modules/project-communication/dtos/project-communication.types.js +3 -0
- package/dist/server/modules/project-communication/dtos/project-communication.types.js.map +1 -0
- package/dist/server/modules/project-communication/project-communication.module.d.ts +2 -0
- package/dist/server/modules/project-communication/project-communication.module.js +24 -0
- package/dist/server/modules/project-communication/project-communication.module.js.map +1 -0
- package/dist/server/modules/project-communication/project-communication.service.d.ts +16 -0
- package/dist/server/modules/project-communication/project-communication.service.js +224 -0
- package/dist/server/modules/project-communication/project-communication.service.js.map +1 -0
- package/dist/server/modules/projects/controllers/projects.controller.d.ts +2 -1
- package/dist/server/modules/projects/controllers/projects.controller.js +3 -10
- package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
- package/dist/server/modules/projects/helpers/project-export.d.ts +1 -0
- package/dist/server/modules/projects/services/projects.service.d.ts +1 -0
- package/dist/server/modules/projects/template-codec/codecs/agents.codec.d.ts +2 -0
- package/dist/server/modules/projects/template-codec/codecs/agents.codec.js +2 -0
- package/dist/server/modules/projects/template-codec/codecs/agents.codec.js.map +1 -1
- package/dist/server/modules/providers/adapters/capabilities/index.d.ts +2 -2
- package/dist/server/modules/providers/adapters/capabilities/index.js +2 -1
- package/dist/server/modules/providers/adapters/capabilities/index.js.map +1 -1
- package/dist/server/modules/providers/adapters/capabilities/provider-plugin.capability.d.ts +6 -0
- package/dist/server/modules/providers/adapters/capabilities/provider-plugin.capability.js +3 -0
- package/dist/server/modules/providers/adapters/capabilities/provider-plugin.capability.js.map +1 -0
- package/dist/server/modules/providers/adapters/capabilities/type-guards.d.ts +3 -0
- package/dist/server/modules/providers/adapters/capabilities/type-guards.js +6 -0
- package/dist/server/modules/providers/adapters/capabilities/type-guards.js.map +1 -1
- package/dist/server/modules/providers/adapters/claude.adapter.d.ts +6 -2
- package/dist/server/modules/providers/adapters/claude.adapter.js +53 -0
- package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/codex.adapter.d.ts +7 -2
- package/dist/server/modules/providers/adapters/codex.adapter.js +46 -0
- package/dist/server/modules/providers/adapters/codex.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/plugin-catalog.utils.d.ts +15 -0
- package/dist/server/modules/providers/adapters/plugin-catalog.utils.js +62 -0
- package/dist/server/modules/providers/adapters/plugin-catalog.utils.js.map +1 -0
- package/dist/server/modules/providers/controllers/provider-plugin-policy.controller.d.ts +18 -0
- package/dist/server/modules/providers/controllers/provider-plugin-policy.controller.js +149 -0
- package/dist/server/modules/providers/controllers/provider-plugin-policy.controller.js.map +1 -0
- package/dist/server/modules/providers/controllers/provider-plugins.controller.d.ts +8 -0
- package/dist/server/modules/providers/controllers/provider-plugins.controller.js +78 -0
- package/dist/server/modules/providers/controllers/provider-plugins.controller.js.map +1 -0
- package/dist/server/modules/providers/dtos/provider-plugin.dto.d.ts +28 -0
- package/dist/server/modules/providers/dtos/provider-plugin.dto.js +3 -0
- package/dist/server/modules/providers/dtos/provider-plugin.dto.js.map +1 -0
- package/dist/server/modules/providers/providers.module.js +15 -1
- package/dist/server/modules/providers/providers.module.js.map +1 -1
- package/dist/server/modules/providers/services/provider-plugin-policy.service.d.ts +20 -0
- package/dist/server/modules/providers/services/provider-plugin-policy.service.js +146 -0
- package/dist/server/modules/providers/services/provider-plugin-policy.service.js.map +1 -0
- package/dist/server/modules/providers/services/provider-plugins.service.d.ts +21 -0
- package/dist/server/modules/providers/services/provider-plugins.service.js +169 -0
- package/dist/server/modules/providers/services/provider-plugins.service.js.map +1 -0
- package/dist/server/modules/registry/controllers/templates.controller.d.ts +2 -1
- package/dist/server/modules/reviews/dtos/review.dto.d.ts +2 -2
- package/dist/server/modules/runtime-context-capture/claude-launch-settings-materializer.service.d.ts +9 -1
- package/dist/server/modules/runtime-context-capture/claude-launch-settings-materializer.service.js +102 -16
- package/dist/server/modules/runtime-context-capture/claude-launch-settings-materializer.service.js.map +1 -1
- package/dist/server/modules/runtime-context-capture/codex-plugin-profile-materializer.service.d.ts +53 -0
- package/dist/server/modules/runtime-context-capture/codex-plugin-profile-materializer.service.js +842 -0
- package/dist/server/modules/runtime-context-capture/codex-plugin-profile-materializer.service.js.map +1 -0
- package/dist/server/modules/runtime-context-capture/index.d.ts +2 -1
- package/dist/server/modules/runtime-context-capture/index.js +5 -1
- package/dist/server/modules/runtime-context-capture/index.js.map +1 -1
- package/dist/server/modules/runtime-context-capture/runtime-context-capture.module.js +11 -2
- package/dist/server/modules/runtime-context-capture/runtime-context-capture.module.js.map +1 -1
- package/dist/server/modules/scheduled-epics/dtos/scheduled-epic.dto.d.ts +1 -1
- package/dist/server/modules/session-reader/data/pricing.json +31 -18
- package/dist/server/modules/sessions/services/delivery-failure-disclosure.d.ts +12 -0
- package/dist/server/modules/sessions/services/delivery-failure-disclosure.js +25 -0
- package/dist/server/modules/sessions/services/delivery-failure-disclosure.js.map +1 -0
- package/dist/server/modules/sessions/services/delivery-failure-notifier.service.js +7 -3
- package/dist/server/modules/sessions/services/delivery-failure-notifier.service.js.map +1 -1
- package/dist/server/modules/sessions/services/message-enqueue.service.d.ts +2 -1
- package/dist/server/modules/sessions/services/message-enqueue.service.js +1 -0
- package/dist/server/modules/sessions/services/message-enqueue.service.js.map +1 -1
- package/dist/server/modules/sessions/services/message-pool.types.d.ts +4 -1
- package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.d.ts +18 -0
- package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js +24 -2
- package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js.map +1 -1
- package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.d.ts +5 -1
- package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js +67 -7
- package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js.map +1 -1
- package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.d.ts +5 -1
- package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.js +65 -5
- package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.js.map +1 -1
- package/dist/server/modules/sessions/services/sessions-message-pool.service.d.ts +2 -1
- package/dist/server/modules/sessions/services/sessions-message-pool.service.js +63 -28
- package/dist/server/modules/sessions/services/sessions-message-pool.service.js.map +1 -1
- package/dist/server/modules/sessions/services/sessions.service.d.ts +4 -1
- package/dist/server/modules/sessions/services/sessions.service.js +19 -2
- package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
- package/dist/server/modules/sessions/utils/profile-options.d.ts +1 -0
- package/dist/server/modules/sessions/utils/profile-options.js +7 -0
- package/dist/server/modules/sessions/utils/profile-options.js.map +1 -1
- package/dist/server/modules/storage/db/schema.d.ts +255 -0
- package/dist/server/modules/storage/db/schema.js +34 -3
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/storage/interfaces/storage.interface.d.ts +15 -2
- package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/agent.delegate.d.ts +1 -0
- package/dist/server/modules/storage/local/delegates/agent.delegate.js +43 -1
- package/dist/server/modules/storage/local/delegates/agent.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/epic.delegate.js +6 -0
- package/dist/server/modules/storage/local/delegates/epic.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/project.delegate.d.ts +1 -0
- package/dist/server/modules/storage/local/delegates/project.delegate.js +21 -0
- package/dist/server/modules/storage/local/delegates/project.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/provider-plugin-policy.delegate.d.ts +13 -0
- package/dist/server/modules/storage/local/delegates/provider-plugin-policy.delegate.js +90 -0
- package/dist/server/modules/storage/local/delegates/provider-plugin-policy.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/local-storage.service.d.ts +12 -1
- package/dist/server/modules/storage/local/local-storage.service.js +33 -0
- package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
- package/dist/server/modules/storage/models/domain.models.d.ts +19 -3
- package/dist/server/modules/subscribers/actions/send-message.action.js +47 -12
- package/dist/server/modules/subscribers/actions/send-message.action.js.map +1 -1
- package/dist/server/modules/subscribers/dtos/subscriber.dto.d.ts +4 -4
- package/dist/server/modules/terminal/services/terminal-registry-rehydrator.service.js +8 -5
- package/dist/server/modules/terminal/services/terminal-registry-rehydrator.service.js.map +1 -1
- package/dist/server/modules/watchers/dtos/watcher.dto.d.ts +2 -2
- package/dist/server/templates/teams-dev.json +5 -5
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/{ReviewDetailPage-yOe1a_fn.js → ReviewDetailPage-Cf5ED0R0.js} +1 -1
- package/dist/server/ui/assets/{ReviewsPage-Bib-IgPo.js → ReviewsPage-BfP9P3QO.js} +1 -1
- package/dist/server/ui/assets/index-B5ZC_DQU.css +32 -0
- package/dist/server/ui/assets/{index-B2TO-QTO.js → index-DptgqA22.js} +241 -241
- package/dist/server/ui/assets/{useReviewSubscription-BmU3yczR.js → useReviewSubscription-QJSfZ3FX.js} +1 -1
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/teams-dev.json +5 -5
- package/package.json +12 -2
- package/dist/server/ui/assets/index-DowJ3ZtQ.css +0 -32
package/dist/server/modules/runtime-context-capture/codex-plugin-profile-materializer.service.js
ADDED
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.CODEX_PROFILE_HELPER_SOURCE = exports.CodexPluginProfileMaterializerService = exports.CODEX_PLUGIN_PROFILE_ROOT = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const crypto_1 = require("crypto");
|
|
18
|
+
const fs_1 = require("fs");
|
|
19
|
+
const promises_1 = require("fs/promises");
|
|
20
|
+
const path_1 = require("path");
|
|
21
|
+
const error_types_1 = require("../../common/errors/error-types");
|
|
22
|
+
const runtime_context_capture_files_1 = require("./runtime-context-capture-files");
|
|
23
|
+
const PRIVATE_DIRECTORY_MODE = 0o700;
|
|
24
|
+
const PRIVATE_FILE_MODE = 0o600;
|
|
25
|
+
const EXECUTABLE_FILE_MODE = 0o700;
|
|
26
|
+
const MAX_PLUGIN_ID_LENGTH = 512;
|
|
27
|
+
const MAX_PROJECT_SLUG_LENGTH = 64;
|
|
28
|
+
const ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
29
|
+
const PROFILE_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,254}$/;
|
|
30
|
+
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
|
|
31
|
+
const NONCE_PATTERN = /^[A-Za-z0-9_-]{16,128}$/;
|
|
32
|
+
exports.CODEX_PLUGIN_PROFILE_ROOT = Symbol('CODEX_PLUGIN_PROFILE_ROOT');
|
|
33
|
+
const CODEX_PROFILE_HELPER_SOURCE = String.raw `#!/usr/bin/env node
|
|
34
|
+
"use strict";
|
|
35
|
+
|
|
36
|
+
const crypto = require("crypto");
|
|
37
|
+
const fs = require("fs");
|
|
38
|
+
const path = require("path");
|
|
39
|
+
|
|
40
|
+
const DIRECTORY_MODE = 0o700;
|
|
41
|
+
const FILE_MODE = 0o600;
|
|
42
|
+
const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,254}$/;
|
|
43
|
+
const DIGEST = /^[0-9a-f]{64}$/;
|
|
44
|
+
const NONCE = /^[A-Za-z0-9_-]{16,128}$/;
|
|
45
|
+
|
|
46
|
+
function fail(code) {
|
|
47
|
+
process.stderr.write("DEVCHAIN_CODEX_PROFILE_ERROR:" + code + "\n");
|
|
48
|
+
process.exit(78);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseArguments(argv) {
|
|
52
|
+
const separator = argv.indexOf("--");
|
|
53
|
+
if (separator === -1) fail("invalid_arguments");
|
|
54
|
+
const control = argv.slice(0, separator);
|
|
55
|
+
const codexArgs = argv.slice(separator + 1);
|
|
56
|
+
const values = Object.create(null);
|
|
57
|
+
for (let index = 0; index < control.length; index += 2) {
|
|
58
|
+
const flag = control[index];
|
|
59
|
+
const value = control[index + 1];
|
|
60
|
+
if (!flag || !flag.startsWith("--") || value === undefined || values[flag] !== undefined) {
|
|
61
|
+
fail("invalid_arguments");
|
|
62
|
+
}
|
|
63
|
+
values[flag] = value;
|
|
64
|
+
}
|
|
65
|
+
const required = [
|
|
66
|
+
"--source",
|
|
67
|
+
"--profile",
|
|
68
|
+
"--project-id",
|
|
69
|
+
"--session-id",
|
|
70
|
+
"--project-digest",
|
|
71
|
+
"--policy-hash",
|
|
72
|
+
"--reference",
|
|
73
|
+
"--locator",
|
|
74
|
+
"--ack",
|
|
75
|
+
"--nonce",
|
|
76
|
+
"--codex-bin"
|
|
77
|
+
];
|
|
78
|
+
if (Object.keys(values).length !== required.length || required.some((flag) => !values[flag])) {
|
|
79
|
+
fail("invalid_arguments");
|
|
80
|
+
}
|
|
81
|
+
return { values, codexArgs };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sha256(content) {
|
|
85
|
+
return crypto.createHash("sha256").update(content, "utf8").digest("hex");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function ensureDirectory(directory) {
|
|
89
|
+
fs.mkdirSync(directory, { recursive: true, mode: DIRECTORY_MODE });
|
|
90
|
+
const stat = fs.lstatSync(directory);
|
|
91
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) fail("unsafe_private_root");
|
|
92
|
+
fs.chmodSync(directory, DIRECTORY_MODE);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ensureCodexHome(directory) {
|
|
96
|
+
fs.mkdirSync(directory, { recursive: true, mode: DIRECTORY_MODE });
|
|
97
|
+
const resolved = fs.realpathSync(directory);
|
|
98
|
+
if (!fs.statSync(resolved).isDirectory()) fail("invalid_codex_home");
|
|
99
|
+
return resolved;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function containedRegularFile(candidate, directory, code) {
|
|
103
|
+
const resolvedDirectory = fs.realpathSync(directory);
|
|
104
|
+
const resolvedCandidate = fs.realpathSync(candidate);
|
|
105
|
+
if (
|
|
106
|
+
path.dirname(resolvedCandidate) !== resolvedDirectory ||
|
|
107
|
+
!fs.lstatSync(resolvedCandidate).isFile() ||
|
|
108
|
+
fs.lstatSync(candidate).isSymbolicLink()
|
|
109
|
+
) {
|
|
110
|
+
fail(code);
|
|
111
|
+
}
|
|
112
|
+
return resolvedCandidate;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function containedDestination(candidate, directory, suffix, code) {
|
|
116
|
+
const resolvedDirectory = fs.realpathSync(directory);
|
|
117
|
+
const resolvedCandidate = path.resolve(candidate);
|
|
118
|
+
if (
|
|
119
|
+
path.dirname(resolvedCandidate) !== resolvedDirectory ||
|
|
120
|
+
!path.basename(resolvedCandidate).endsWith(suffix)
|
|
121
|
+
) {
|
|
122
|
+
fail(code);
|
|
123
|
+
}
|
|
124
|
+
return resolvedCandidate;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function writeExclusive(destination, content) {
|
|
128
|
+
const temporary = destination + ".tmp-" + process.pid + "-" + crypto.randomUUID();
|
|
129
|
+
let descriptor;
|
|
130
|
+
try {
|
|
131
|
+
descriptor = fs.openSync(temporary, "wx", FILE_MODE);
|
|
132
|
+
fs.writeFileSync(descriptor, content, { encoding: "utf8" });
|
|
133
|
+
fs.fsyncSync(descriptor);
|
|
134
|
+
fs.closeSync(descriptor);
|
|
135
|
+
descriptor = undefined;
|
|
136
|
+
try {
|
|
137
|
+
fs.linkSync(temporary, destination);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (!error || error.code !== "EEXIST") throw error;
|
|
140
|
+
fail("lifecycle_collision");
|
|
141
|
+
}
|
|
142
|
+
fs.chmodSync(destination, FILE_MODE);
|
|
143
|
+
} finally {
|
|
144
|
+
if (descriptor !== undefined) {
|
|
145
|
+
try { fs.closeSync(descriptor); } catch {}
|
|
146
|
+
}
|
|
147
|
+
try { fs.unlinkSync(temporary); } catch {}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function processStartIdentity(pid) {
|
|
152
|
+
try {
|
|
153
|
+
const value = fs.readFileSync("/proc/" + pid + "/stat", "utf8");
|
|
154
|
+
const fields = value.slice(value.lastIndexOf(")") + 2).trim().split(/\s+/);
|
|
155
|
+
return fields[19] || null;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (error && (error.code === "ENOENT" || error.code === "ESRCH")) return false;
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function sleep(milliseconds) {
|
|
163
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function acquireTargetLock(locksRoot, canonicalTargetPath, attemptNonce) {
|
|
167
|
+
const lockKey = sha256(canonicalTargetPath);
|
|
168
|
+
const lockPath = path.join(locksRoot, lockKey + ".lock");
|
|
169
|
+
const deadline = Date.now() + 5000;
|
|
170
|
+
while (Date.now() < deadline) {
|
|
171
|
+
try {
|
|
172
|
+
fs.mkdirSync(lockPath, { mode: DIRECTORY_MODE });
|
|
173
|
+
const startIdentity = processStartIdentity(process.pid);
|
|
174
|
+
if (typeof startIdentity !== "string") fail("process_identity_unavailable");
|
|
175
|
+
fs.writeFileSync(
|
|
176
|
+
path.join(lockPath, "owner.json"),
|
|
177
|
+
JSON.stringify({ version: 1, pid: process.pid, startIdentity, attemptNonce }),
|
|
178
|
+
{ encoding: "utf8", mode: FILE_MODE, flag: "wx" }
|
|
179
|
+
);
|
|
180
|
+
return {
|
|
181
|
+
lockKey,
|
|
182
|
+
release() {
|
|
183
|
+
try { fs.rmSync(lockPath, { recursive: true, force: true }); } catch {}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (!error || error.code !== "EEXIST") fail("lock_failure");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let owner;
|
|
191
|
+
try {
|
|
192
|
+
owner = JSON.parse(fs.readFileSync(path.join(lockPath, "owner.json"), "utf8"));
|
|
193
|
+
} catch {
|
|
194
|
+
sleep(25);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const actual = Number.isSafeInteger(owner.pid) ? processStartIdentity(owner.pid) : null;
|
|
198
|
+
if (actual === false || (typeof actual === "string" && actual !== owner.startIdentity)) {
|
|
199
|
+
const stalePath = lockPath + ".stale-" + process.pid + "-" + crypto.randomUUID();
|
|
200
|
+
try {
|
|
201
|
+
fs.renameSync(lockPath, stalePath);
|
|
202
|
+
fs.rmSync(stalePath, { recursive: true, force: true });
|
|
203
|
+
continue;
|
|
204
|
+
} catch {
|
|
205
|
+
sleep(25);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
sleep(25);
|
|
210
|
+
}
|
|
211
|
+
fail("lock_timeout");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function writeOrValidateMarker(markerPath, marker) {
|
|
215
|
+
try {
|
|
216
|
+
const metadata = fs.lstatSync(markerPath);
|
|
217
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) fail("unsafe_target_marker");
|
|
218
|
+
const existing = JSON.parse(fs.readFileSync(markerPath, "utf8"));
|
|
219
|
+
if (
|
|
220
|
+
existing.canonicalTargetPath !== marker.canonicalTargetPath ||
|
|
221
|
+
existing.profileName !== marker.profileName ||
|
|
222
|
+
existing.policyHash !== marker.policyHash
|
|
223
|
+
) {
|
|
224
|
+
fail("target_marker_mismatch");
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (!error || error.code !== "ENOENT") throw error;
|
|
229
|
+
}
|
|
230
|
+
writeExclusive(markerPath, JSON.stringify(marker));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function materializeProfile(destination, content) {
|
|
234
|
+
try {
|
|
235
|
+
const stat = fs.lstatSync(destination);
|
|
236
|
+
if (!stat.isFile() || stat.isSymbolicLink()) fail("unsafe_profile_target");
|
|
237
|
+
if (fs.readFileSync(destination, "utf8") !== content) fail("profile_content_mismatch");
|
|
238
|
+
fs.chmodSync(destination, FILE_MODE);
|
|
239
|
+
return;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (!error || error.code !== "ENOENT") throw error;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const temporary = destination + ".tmp-" + process.pid + "-" + crypto.randomUUID();
|
|
245
|
+
let descriptor;
|
|
246
|
+
try {
|
|
247
|
+
descriptor = fs.openSync(temporary, "wx", FILE_MODE);
|
|
248
|
+
fs.writeFileSync(descriptor, content, { encoding: "utf8" });
|
|
249
|
+
fs.fsyncSync(descriptor);
|
|
250
|
+
fs.closeSync(descriptor);
|
|
251
|
+
descriptor = undefined;
|
|
252
|
+
try {
|
|
253
|
+
fs.linkSync(temporary, destination);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (!error || error.code !== "EEXIST") throw error;
|
|
256
|
+
const stat = fs.lstatSync(destination);
|
|
257
|
+
if (
|
|
258
|
+
!stat.isFile() ||
|
|
259
|
+
stat.isSymbolicLink() ||
|
|
260
|
+
fs.readFileSync(destination, "utf8") !== content
|
|
261
|
+
) {
|
|
262
|
+
fail("profile_content_mismatch");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
fs.chmodSync(destination, FILE_MODE);
|
|
266
|
+
} finally {
|
|
267
|
+
if (descriptor !== undefined) {
|
|
268
|
+
try { fs.closeSync(descriptor); } catch {}
|
|
269
|
+
}
|
|
270
|
+
try { fs.unlinkSync(temporary); } catch {}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function resolveExecutable(binary) {
|
|
275
|
+
if (path.isAbsolute(binary)) {
|
|
276
|
+
try { fs.accessSync(binary, fs.constants.X_OK); } catch { fail("codex_binary_unavailable"); }
|
|
277
|
+
return binary;
|
|
278
|
+
}
|
|
279
|
+
if (binary.includes("/") || binary.includes("\\") || binary.length === 0) {
|
|
280
|
+
fail("invalid_codex_binary");
|
|
281
|
+
}
|
|
282
|
+
for (const entry of (process.env.PATH || "").split(path.delimiter)) {
|
|
283
|
+
const candidate = path.resolve(entry || process.cwd(), binary);
|
|
284
|
+
try {
|
|
285
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
286
|
+
if (fs.statSync(candidate).isFile()) return candidate;
|
|
287
|
+
} catch {}
|
|
288
|
+
}
|
|
289
|
+
fail("codex_binary_unavailable");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function main() {
|
|
293
|
+
const { values, codexArgs } = parseArguments(process.argv.slice(2));
|
|
294
|
+
const profileName = values["--profile"];
|
|
295
|
+
const projectDigest = values["--project-digest"];
|
|
296
|
+
const policyHash = values["--policy-hash"];
|
|
297
|
+
const nonce = values["--nonce"];
|
|
298
|
+
if (!PROFILE_NAME.test(profileName)) fail("invalid_profile_name");
|
|
299
|
+
if (!DIGEST.test(projectDigest) || !DIGEST.test(policyHash)) fail("invalid_digest");
|
|
300
|
+
if (!NONCE.test(nonce)) fail("invalid_nonce");
|
|
301
|
+
if (sha256(values["--project-id"]) !== projectDigest) fail("project_digest_mismatch");
|
|
302
|
+
if (!profileName.endsWith("-" + projectDigest.slice(0, 16) + "-" + policyHash)) {
|
|
303
|
+
fail("profile_identity_mismatch");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const privateRoot = path.dirname(path.dirname(fs.realpathSync(__filename)));
|
|
307
|
+
const sourceRoot = path.join(privateRoot, "source-revisions");
|
|
308
|
+
const lifecycleRoot = path.join(privateRoot, "lifecycle");
|
|
309
|
+
const locksRoot = path.join(privateRoot, "locks");
|
|
310
|
+
const targetsRoot = path.join(privateRoot, "targets");
|
|
311
|
+
ensureDirectory(sourceRoot);
|
|
312
|
+
ensureDirectory(lifecycleRoot);
|
|
313
|
+
ensureDirectory(locksRoot);
|
|
314
|
+
ensureDirectory(targetsRoot);
|
|
315
|
+
const sourcePath = containedRegularFile(values["--source"], sourceRoot, "unsafe_source_path");
|
|
316
|
+
if (path.basename(sourcePath) !== profileName + ".config.toml") fail("source_identity_mismatch");
|
|
317
|
+
const referencePath = containedDestination(
|
|
318
|
+
values["--reference"],
|
|
319
|
+
lifecycleRoot,
|
|
320
|
+
".reference.json",
|
|
321
|
+
"unsafe_reference_path"
|
|
322
|
+
);
|
|
323
|
+
const locatorPath = containedDestination(
|
|
324
|
+
values["--locator"],
|
|
325
|
+
lifecycleRoot,
|
|
326
|
+
".locator.json",
|
|
327
|
+
"unsafe_locator_path"
|
|
328
|
+
);
|
|
329
|
+
const acknowledgementPath = containedDestination(
|
|
330
|
+
values["--ack"],
|
|
331
|
+
lifecycleRoot,
|
|
332
|
+
".ack.json",
|
|
333
|
+
"unsafe_ack_path"
|
|
334
|
+
);
|
|
335
|
+
const attemptKey = sha256(values["--project-id"] + "\0" + nonce);
|
|
336
|
+
if (referencePath !== path.join(lifecycleRoot, attemptKey + ".reference.json")) {
|
|
337
|
+
fail("reference_identity_mismatch");
|
|
338
|
+
}
|
|
339
|
+
if (acknowledgementPath !== path.join(lifecycleRoot, attemptKey + ".ack.json")) {
|
|
340
|
+
fail("ack_identity_mismatch");
|
|
341
|
+
}
|
|
342
|
+
if (locatorPath !== path.join(lifecycleRoot, attemptKey + ".locator.json")) {
|
|
343
|
+
fail("locator_identity_mismatch");
|
|
344
|
+
}
|
|
345
|
+
const content = fs.readFileSync(sourcePath, "utf8");
|
|
346
|
+
if (sha256(content) !== policyHash) fail("source_hash_mismatch");
|
|
347
|
+
|
|
348
|
+
const configuredHome = process.env.CODEX_HOME;
|
|
349
|
+
const inheritedHome = process.env.HOME;
|
|
350
|
+
const homeCandidate = configuredHome && configuredHome.length > 0
|
|
351
|
+
? configuredHome
|
|
352
|
+
: inheritedHome && inheritedHome.length > 0
|
|
353
|
+
? path.join(inheritedHome, ".codex")
|
|
354
|
+
: null;
|
|
355
|
+
if (!homeCandidate) fail("codex_home_unavailable");
|
|
356
|
+
const codexHome = ensureCodexHome(path.resolve(homeCandidate));
|
|
357
|
+
const canonicalTargetPath = path.join(codexHome, profileName + ".config.toml");
|
|
358
|
+
if (path.dirname(canonicalTargetPath) !== codexHome) fail("profile_path_escape");
|
|
359
|
+
const codexBinary = resolveExecutable(values["--codex-bin"]);
|
|
360
|
+
if (typeof process.execve !== "function") fail("exec_unavailable");
|
|
361
|
+
const targetLock = acquireTargetLock(locksRoot, canonicalTargetPath, nonce);
|
|
362
|
+
try {
|
|
363
|
+
materializeProfile(canonicalTargetPath, content);
|
|
364
|
+
if (sha256(fs.readFileSync(canonicalTargetPath, "utf8")) !== policyHash) {
|
|
365
|
+
fail("target_hash_mismatch");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const acknowledgement = {
|
|
369
|
+
version: 1,
|
|
370
|
+
canonicalTargetPath,
|
|
371
|
+
projectId: values["--project-id"],
|
|
372
|
+
sessionId: values["--session-id"],
|
|
373
|
+
projectDigest,
|
|
374
|
+
profileName,
|
|
375
|
+
policyHash,
|
|
376
|
+
nonce
|
|
377
|
+
};
|
|
378
|
+
const markerPath = path.join(targetsRoot, targetLock.lockKey + ".target.json");
|
|
379
|
+
writeOrValidateMarker(markerPath, acknowledgement);
|
|
380
|
+
writeExclusive(referencePath, JSON.stringify({
|
|
381
|
+
...acknowledgement,
|
|
382
|
+
acknowledgementPath,
|
|
383
|
+
locatorPath
|
|
384
|
+
}));
|
|
385
|
+
writeExclusive(locatorPath, JSON.stringify({
|
|
386
|
+
...acknowledgement,
|
|
387
|
+
referencePath,
|
|
388
|
+
acknowledgementPath,
|
|
389
|
+
markerPath,
|
|
390
|
+
lockKey: targetLock.lockKey
|
|
391
|
+
}));
|
|
392
|
+
writeExclusive(acknowledgementPath, JSON.stringify(acknowledgement));
|
|
393
|
+
} finally {
|
|
394
|
+
targetLock.release();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
process.execve(codexBinary, [values["--codex-bin"], ...codexArgs], process.env);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
main();
|
|
402
|
+
} catch {
|
|
403
|
+
fail("internal_failure");
|
|
404
|
+
}
|
|
405
|
+
`;
|
|
406
|
+
exports.CODEX_PROFILE_HELPER_SOURCE = CODEX_PROFILE_HELPER_SOURCE;
|
|
407
|
+
let CodexPluginProfileMaterializerService = class CodexPluginProfileMaterializerService {
|
|
408
|
+
rootPath;
|
|
409
|
+
constructor(rootPath) {
|
|
410
|
+
this.rootPath = (0, path_1.resolve)(rootPath ?? (0, path_1.join)((0, runtime_context_capture_files_1.getRuntimeContextCaptureRoot)(), 'codex-plugin-profiles'));
|
|
411
|
+
}
|
|
412
|
+
async prepare(input) {
|
|
413
|
+
if (input.pluginPolicy.length === 0)
|
|
414
|
+
return null;
|
|
415
|
+
this.validateIdentity(input);
|
|
416
|
+
const profileToml = this.serializePolicy(input.pluginPolicy);
|
|
417
|
+
const projectDigest = this.sha256(input.projectId);
|
|
418
|
+
const policyHash = this.sha256(profileToml);
|
|
419
|
+
const projectSlug = this.toProjectSlug(input.projectName);
|
|
420
|
+
const profileName = `devchain-${projectSlug}-${projectDigest.slice(0, 16)}-${policyHash}`;
|
|
421
|
+
if (!PROFILE_NAME_PATTERN.test(profileName) || profileName.length > 255) {
|
|
422
|
+
throw new error_types_1.ValidationError('Codex plugin profile name is not filesystem-safe.', {
|
|
423
|
+
field: 'projectName',
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
const sourceRoot = (0, path_1.join)(this.rootPath, 'source-revisions');
|
|
427
|
+
const helperRoot = (0, path_1.join)(this.rootPath, 'bin');
|
|
428
|
+
const lifecycleRoot = (0, path_1.join)(this.rootPath, 'lifecycle');
|
|
429
|
+
const locksRoot = (0, path_1.join)(this.rootPath, 'locks');
|
|
430
|
+
const targetsRoot = (0, path_1.join)(this.rootPath, 'targets');
|
|
431
|
+
await Promise.all([
|
|
432
|
+
this.ensurePrivateDirectory(this.rootPath),
|
|
433
|
+
this.ensurePrivateDirectory(sourceRoot),
|
|
434
|
+
this.ensurePrivateDirectory(helperRoot),
|
|
435
|
+
this.ensurePrivateDirectory(lifecycleRoot),
|
|
436
|
+
this.ensurePrivateDirectory(locksRoot),
|
|
437
|
+
this.ensurePrivateDirectory(targetsRoot),
|
|
438
|
+
]);
|
|
439
|
+
const helperHash = this.sha256(CODEX_PROFILE_HELPER_SOURCE);
|
|
440
|
+
const sourceRevisionPath = (0, path_1.join)(sourceRoot, `${profileName}.config.toml`);
|
|
441
|
+
const helperPath = (0, path_1.join)(helperRoot, `devchain-codex-profile-helper-${helperHash}`);
|
|
442
|
+
const attemptKey = this.sha256(`${input.projectId}\0${input.attemptNonce}`);
|
|
443
|
+
const referencePath = (0, path_1.join)(lifecycleRoot, `${attemptKey}.reference.json`);
|
|
444
|
+
const locatorPath = (0, path_1.join)(lifecycleRoot, `${attemptKey}.locator.json`);
|
|
445
|
+
const acknowledgementPath = (0, path_1.join)(lifecycleRoot, `${attemptKey}.ack.json`);
|
|
446
|
+
await Promise.all([
|
|
447
|
+
this.materializeImmutable(sourceRevisionPath, profileToml, PRIVATE_FILE_MODE),
|
|
448
|
+
this.materializeImmutable(helperPath, CODEX_PROFILE_HELPER_SOURCE, EXECUTABLE_FILE_MODE),
|
|
449
|
+
]);
|
|
450
|
+
return {
|
|
451
|
+
profileName,
|
|
452
|
+
projectDigest,
|
|
453
|
+
policyHash,
|
|
454
|
+
sourceRevisionPath,
|
|
455
|
+
helperPath,
|
|
456
|
+
sessionId: input.sessionId,
|
|
457
|
+
attemptNonce: input.attemptNonce,
|
|
458
|
+
referencePath,
|
|
459
|
+
locatorPath,
|
|
460
|
+
acknowledgementPath,
|
|
461
|
+
providerOptionArgs: ['--profile', profileName],
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
buildHelperArgv(prepared, codexBinary, codexArgv, input) {
|
|
465
|
+
this.validateCodexBinary(codexBinary);
|
|
466
|
+
if (!PROFILE_NAME_PATTERN.test(prepared.profileName)) {
|
|
467
|
+
throw new error_types_1.ValidationError('Codex plugin profile name is not filesystem-safe.', {
|
|
468
|
+
field: 'profileName',
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
if (!DIGEST_PATTERN.test(prepared.projectDigest) || !DIGEST_PATTERN.test(prepared.policyHash)) {
|
|
472
|
+
throw new error_types_1.ValidationError('Codex plugin profile digests are invalid.', {
|
|
473
|
+
field: 'policyHash',
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (!NONCE_PATTERN.test(input.attemptNonce)) {
|
|
477
|
+
throw new error_types_1.ValidationError('Codex plugin profile attempt nonce is invalid.', {
|
|
478
|
+
field: 'attemptNonce',
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
if (codexArgv.some((argument) => typeof argument !== 'string' || argument.includes('\0'))) {
|
|
482
|
+
throw new error_types_1.ValidationError('Codex launch arguments are invalid.', { field: 'codexArgv' });
|
|
483
|
+
}
|
|
484
|
+
return [
|
|
485
|
+
prepared.helperPath,
|
|
486
|
+
'--source',
|
|
487
|
+
prepared.sourceRevisionPath,
|
|
488
|
+
'--profile',
|
|
489
|
+
prepared.profileName,
|
|
490
|
+
'--project-id',
|
|
491
|
+
input.projectId,
|
|
492
|
+
'--session-id',
|
|
493
|
+
prepared.sessionId,
|
|
494
|
+
'--project-digest',
|
|
495
|
+
prepared.projectDigest,
|
|
496
|
+
'--policy-hash',
|
|
497
|
+
prepared.policyHash,
|
|
498
|
+
'--reference',
|
|
499
|
+
prepared.referencePath,
|
|
500
|
+
'--locator',
|
|
501
|
+
prepared.locatorPath,
|
|
502
|
+
'--ack',
|
|
503
|
+
prepared.acknowledgementPath,
|
|
504
|
+
'--nonce',
|
|
505
|
+
input.attemptNonce,
|
|
506
|
+
'--codex-bin',
|
|
507
|
+
codexBinary,
|
|
508
|
+
'--',
|
|
509
|
+
...codexArgv,
|
|
510
|
+
];
|
|
511
|
+
}
|
|
512
|
+
async awaitAcknowledgement(prepared, input, timeoutMs = 5_000) {
|
|
513
|
+
const deadline = Date.now() + timeoutMs;
|
|
514
|
+
while (Date.now() <= deadline) {
|
|
515
|
+
const acknowledgement = await this.readJsonFile(prepared.acknowledgementPath);
|
|
516
|
+
if (acknowledgement) {
|
|
517
|
+
const locator = await this.readJsonFile(prepared.locatorPath);
|
|
518
|
+
if (!locator) {
|
|
519
|
+
throw new error_types_1.IOError('Codex profile acknowledgement has no matching locator.', {
|
|
520
|
+
stage: 'profile_acknowledgement',
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
this.assertAcknowledgement(acknowledgement, prepared, input);
|
|
524
|
+
this.assertAcknowledgement(locator, prepared, input);
|
|
525
|
+
if (locator.canonicalTargetPath !== acknowledgement.canonicalTargetPath ||
|
|
526
|
+
locator.referencePath !== prepared.referencePath ||
|
|
527
|
+
locator.acknowledgementPath !== prepared.acknowledgementPath ||
|
|
528
|
+
locator.lockKey !== this.sha256(locator.canonicalTargetPath)) {
|
|
529
|
+
throw new error_types_1.IOError('Codex profile locator does not match its launch attempt.', {
|
|
530
|
+
stage: 'profile_locator',
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return acknowledgement.canonicalTargetPath;
|
|
534
|
+
}
|
|
535
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, 25));
|
|
536
|
+
}
|
|
537
|
+
throw new error_types_1.TimeoutError('Timed out waiting for Codex profile acknowledgement.', {
|
|
538
|
+
stage: 'profile_acknowledgement',
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
async cleanupPrepared(prepared) {
|
|
542
|
+
const locator = await this.readJsonFile(prepared.locatorPath);
|
|
543
|
+
if (!locator) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
await this.cleanupLocator(prepared.locatorPath, locator);
|
|
547
|
+
}
|
|
548
|
+
async cleanupSession(sessionId) {
|
|
549
|
+
await this.cleanupLifecycleLocators((locator) => locator.sessionId === sessionId);
|
|
550
|
+
}
|
|
551
|
+
async reconcileStartup(nonLiveSessionIds) {
|
|
552
|
+
await this.cleanupLifecycleLocators((locator) => nonLiveSessionIds.has(locator.sessionId));
|
|
553
|
+
}
|
|
554
|
+
serializePolicy(policy) {
|
|
555
|
+
const byPluginId = new Map();
|
|
556
|
+
for (const entry of policy) {
|
|
557
|
+
this.validatePolicyEntry(entry);
|
|
558
|
+
if (byPluginId.has(entry.pluginId)) {
|
|
559
|
+
throw new error_types_1.ValidationError('Resolved Codex plugin policy contains duplicate plugin IDs.', {
|
|
560
|
+
field: 'pluginPolicy',
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
byPluginId.set(entry.pluginId, entry.enabled);
|
|
564
|
+
}
|
|
565
|
+
return [...byPluginId.entries()]
|
|
566
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
567
|
+
.map(([pluginId, enabled]) => `[plugins.${JSON.stringify(pluginId)}]\nenabled = ${enabled ? 'true' : 'false'}\n`)
|
|
568
|
+
.join('\n');
|
|
569
|
+
}
|
|
570
|
+
async cleanupLifecycleLocators(predicate) {
|
|
571
|
+
const lifecycleRoot = (0, path_1.join)(this.rootPath, 'lifecycle');
|
|
572
|
+
let entries;
|
|
573
|
+
try {
|
|
574
|
+
entries = await (0, promises_1.readdir)(lifecycleRoot, { withFileTypes: true });
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
if (error.code === 'ENOENT')
|
|
578
|
+
return;
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
for (const entry of entries) {
|
|
582
|
+
if (!entry.isFile() || !entry.name.endsWith('.locator.json'))
|
|
583
|
+
continue;
|
|
584
|
+
const locatorPath = (0, path_1.join)(lifecycleRoot, entry.name);
|
|
585
|
+
const locator = await this.readJsonFile(locatorPath);
|
|
586
|
+
if (!locator || !predicate(locator))
|
|
587
|
+
continue;
|
|
588
|
+
await this.cleanupLocator(locatorPath, locator);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
async cleanupLocator(locatorPath, locator) {
|
|
592
|
+
if (!(0, path_1.isAbsolute)(locator.canonicalTargetPath) ||
|
|
593
|
+
locator.lockKey !== this.sha256(locator.canonicalTargetPath) ||
|
|
594
|
+
locatorPath !==
|
|
595
|
+
(0, path_1.join)(this.rootPath, 'lifecycle', `${this.sha256(`${locator.projectId}\0${locator.nonce}`)}.locator.json`) ||
|
|
596
|
+
locator.referencePath !== locatorPath.replace(/\.locator\.json$/, '.reference.json') ||
|
|
597
|
+
locator.acknowledgementPath !== locatorPath.replace(/\.locator\.json$/, '.ack.json') ||
|
|
598
|
+
locator.markerPath !== (0, path_1.join)(this.rootPath, 'targets', `${locator.lockKey}.target.json`)) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const release = await this.acquireTargetLock(locator.canonicalTargetPath, `cleanup:${locator.nonce}`);
|
|
602
|
+
try {
|
|
603
|
+
await (0, promises_1.rm)(locator.referencePath, { force: true });
|
|
604
|
+
if (!(await this.hasLiveReference(locator.canonicalTargetPath))) {
|
|
605
|
+
const marker = await this.readJsonFile(locator.markerPath);
|
|
606
|
+
const targetHash = await this.hashRegularFile(locator.canonicalTargetPath);
|
|
607
|
+
if (marker &&
|
|
608
|
+
marker.canonicalTargetPath === locator.canonicalTargetPath &&
|
|
609
|
+
marker.profileName === locator.profileName &&
|
|
610
|
+
marker.policyHash === locator.policyHash &&
|
|
611
|
+
targetHash === locator.policyHash) {
|
|
612
|
+
await (0, promises_1.rm)(locator.canonicalTargetPath, { force: true });
|
|
613
|
+
await (0, promises_1.rm)(locator.markerPath, { force: true });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
finally {
|
|
618
|
+
await release();
|
|
619
|
+
}
|
|
620
|
+
await Promise.all([
|
|
621
|
+
(0, promises_1.rm)(locator.acknowledgementPath, { force: true }),
|
|
622
|
+
(0, promises_1.rm)(locatorPath, { force: true }),
|
|
623
|
+
]);
|
|
624
|
+
}
|
|
625
|
+
async hasLiveReference(canonicalTargetPath) {
|
|
626
|
+
const lifecycleRoot = (0, path_1.join)(this.rootPath, 'lifecycle');
|
|
627
|
+
const entries = await (0, promises_1.readdir)(lifecycleRoot, { withFileTypes: true });
|
|
628
|
+
for (const entry of entries) {
|
|
629
|
+
if (!entry.isFile() || !entry.name.endsWith('.reference.json'))
|
|
630
|
+
continue;
|
|
631
|
+
const reference = await this.readJsonFile((0, path_1.join)(lifecycleRoot, entry.name));
|
|
632
|
+
if (!reference || reference.canonicalTargetPath === canonicalTargetPath)
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
async acquireTargetLock(canonicalTargetPath, attemptNonce) {
|
|
638
|
+
const locksRoot = (0, path_1.join)(this.rootPath, 'locks');
|
|
639
|
+
await this.ensurePrivateDirectory(locksRoot);
|
|
640
|
+
const lockPath = (0, path_1.join)(locksRoot, `${this.sha256(canonicalTargetPath)}.lock`);
|
|
641
|
+
const deadline = Date.now() + 5_000;
|
|
642
|
+
while (Date.now() < deadline) {
|
|
643
|
+
try {
|
|
644
|
+
await (0, promises_1.mkdir)(lockPath, { mode: PRIVATE_DIRECTORY_MODE });
|
|
645
|
+
const startIdentity = await this.readProcessStartIdentity(process.pid);
|
|
646
|
+
if (typeof startIdentity !== 'string') {
|
|
647
|
+
await (0, promises_1.rm)(lockPath, { recursive: true, force: true });
|
|
648
|
+
throw new error_types_1.IOError('Process identity unavailable for Codex profile lock.');
|
|
649
|
+
}
|
|
650
|
+
await (0, promises_1.writeFile)((0, path_1.join)(lockPath, 'owner.json'), JSON.stringify({ version: 1, pid: process.pid, startIdentity, attemptNonce }), { encoding: 'utf8', mode: PRIVATE_FILE_MODE, flag: 'wx' });
|
|
651
|
+
return async () => (0, promises_1.rm)(lockPath, { recursive: true, force: true });
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
if (error.code !== 'EEXIST')
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
const owner = await this.readJsonFile((0, path_1.join)(lockPath, 'owner.json'));
|
|
658
|
+
if (owner && Number.isSafeInteger(owner.pid) && typeof owner.startIdentity === 'string') {
|
|
659
|
+
const actual = await this.readProcessStartIdentity(owner.pid);
|
|
660
|
+
if (actual === false || (typeof actual === 'string' && actual !== owner.startIdentity)) {
|
|
661
|
+
const stalePath = `${lockPath}.stale-${process.pid}-${(0, crypto_1.randomUUID)()}`;
|
|
662
|
+
try {
|
|
663
|
+
await (0, promises_1.rename)(lockPath, stalePath);
|
|
664
|
+
await (0, promises_1.rm)(stalePath, { recursive: true, force: true });
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
catch {
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, 25));
|
|
672
|
+
}
|
|
673
|
+
throw new error_types_1.TimeoutError('Timed out acquiring Codex profile lifecycle lock.', {
|
|
674
|
+
stage: 'profile_lock',
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
async readProcessStartIdentity(pid) {
|
|
678
|
+
try {
|
|
679
|
+
const value = await (0, promises_1.readFile)(`/proc/${pid}/stat`, 'utf8');
|
|
680
|
+
return (value
|
|
681
|
+
.slice(value.lastIndexOf(')') + 2)
|
|
682
|
+
.trim()
|
|
683
|
+
.split(/\s+/)[19] ?? null);
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
const code = error.code;
|
|
687
|
+
return code === 'ENOENT' || code === 'ESRCH' ? false : null;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
async readJsonFile(path) {
|
|
691
|
+
try {
|
|
692
|
+
const metadata = await (0, promises_1.lstat)(path);
|
|
693
|
+
if (!metadata.isFile() || metadata.isSymbolicLink())
|
|
694
|
+
return null;
|
|
695
|
+
return JSON.parse(await (0, promises_1.readFile)(path, 'utf8'));
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
if (error.code === 'ENOENT')
|
|
699
|
+
return null;
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async hashRegularFile(path) {
|
|
704
|
+
try {
|
|
705
|
+
const metadata = await (0, promises_1.lstat)(path);
|
|
706
|
+
if (!metadata.isFile() || metadata.isSymbolicLink())
|
|
707
|
+
return null;
|
|
708
|
+
return this.sha256(await (0, promises_1.readFile)(path, 'utf8'));
|
|
709
|
+
}
|
|
710
|
+
catch {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
assertAcknowledgement(acknowledgement, prepared, input) {
|
|
715
|
+
if (acknowledgement.version !== 1 ||
|
|
716
|
+
acknowledgement.projectId !== input.projectId ||
|
|
717
|
+
acknowledgement.sessionId !== prepared.sessionId ||
|
|
718
|
+
acknowledgement.projectDigest !== prepared.projectDigest ||
|
|
719
|
+
acknowledgement.profileName !== prepared.profileName ||
|
|
720
|
+
acknowledgement.policyHash !== prepared.policyHash ||
|
|
721
|
+
acknowledgement.nonce !== input.attemptNonce ||
|
|
722
|
+
!(0, path_1.isAbsolute)(acknowledgement.canonicalTargetPath)) {
|
|
723
|
+
throw new error_types_1.IOError('Codex profile acknowledgement does not match its launch attempt.', {
|
|
724
|
+
stage: 'profile_acknowledgement',
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
validateIdentity(input) {
|
|
729
|
+
if (typeof input.projectId !== 'string' ||
|
|
730
|
+
input.projectId.length === 0 ||
|
|
731
|
+
ASCII_CONTROL_PATTERN.test(input.projectId)) {
|
|
732
|
+
throw new error_types_1.ValidationError('Codex plugin profile project identity is invalid.', {
|
|
733
|
+
field: 'projectId',
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
if (typeof input.projectName !== 'string') {
|
|
737
|
+
throw new error_types_1.ValidationError('Codex plugin profile project name is invalid.', {
|
|
738
|
+
field: 'projectName',
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
if (typeof input.sessionId !== 'string' ||
|
|
742
|
+
input.sessionId.length === 0 ||
|
|
743
|
+
ASCII_CONTROL_PATTERN.test(input.sessionId)) {
|
|
744
|
+
throw new error_types_1.ValidationError('Codex plugin profile session identity is invalid.', {
|
|
745
|
+
field: 'sessionId',
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
if (!NONCE_PATTERN.test(input.attemptNonce)) {
|
|
749
|
+
throw new error_types_1.ValidationError('Codex plugin profile attempt nonce is invalid.', {
|
|
750
|
+
field: 'attemptNonce',
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
validatePolicyEntry(entry) {
|
|
755
|
+
const characterCount = typeof entry.pluginId === 'string' ? Array.from(entry.pluginId).length : 0;
|
|
756
|
+
if (typeof entry.pluginId !== 'string' ||
|
|
757
|
+
characterCount === 0 ||
|
|
758
|
+
characterCount > MAX_PLUGIN_ID_LENGTH ||
|
|
759
|
+
ASCII_CONTROL_PATTERN.test(entry.pluginId) ||
|
|
760
|
+
typeof entry.enabled !== 'boolean') {
|
|
761
|
+
throw new error_types_1.ValidationError('Resolved Codex plugin policy entry is invalid.', {
|
|
762
|
+
field: 'pluginPolicy',
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
validateCodexBinary(codexBinary) {
|
|
767
|
+
if (typeof codexBinary !== 'string' ||
|
|
768
|
+
codexBinary.length === 0 ||
|
|
769
|
+
ASCII_CONTROL_PATTERN.test(codexBinary) ||
|
|
770
|
+
(!(0, path_1.isAbsolute)(codexBinary) && (codexBinary.includes('/') || codexBinary.includes('\\')))) {
|
|
771
|
+
throw new error_types_1.ValidationError('Codex binary must be a bare command or absolute path.', {
|
|
772
|
+
field: 'codexBinary',
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
toProjectSlug(projectName) {
|
|
777
|
+
const slug = projectName
|
|
778
|
+
.normalize('NFKD')
|
|
779
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
780
|
+
.toLowerCase()
|
|
781
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
782
|
+
.replace(/^-+|-+$/g, '')
|
|
783
|
+
.slice(0, MAX_PROJECT_SLUG_LENGTH)
|
|
784
|
+
.replace(/-+$/g, '');
|
|
785
|
+
return slug || 'project';
|
|
786
|
+
}
|
|
787
|
+
sha256(content) {
|
|
788
|
+
return (0, crypto_1.createHash)('sha256').update(content, 'utf8').digest('hex');
|
|
789
|
+
}
|
|
790
|
+
async ensurePrivateDirectory(path) {
|
|
791
|
+
await (0, promises_1.mkdir)(path, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
|
|
792
|
+
const metadata = await (0, promises_1.lstat)(path);
|
|
793
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
794
|
+
throw new Error('Codex plugin profile private path is unsafe');
|
|
795
|
+
}
|
|
796
|
+
await (0, promises_1.chmod)(path, PRIVATE_DIRECTORY_MODE);
|
|
797
|
+
}
|
|
798
|
+
async materializeImmutable(path, content, mode) {
|
|
799
|
+
try {
|
|
800
|
+
await (0, promises_1.access)(path, fs_1.constants.F_OK);
|
|
801
|
+
const metadata = await (0, promises_1.lstat)(path);
|
|
802
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
803
|
+
throw new Error('Immutable Codex profile artifact path is unsafe');
|
|
804
|
+
}
|
|
805
|
+
if ((await (0, promises_1.readFile)(path, 'utf8')) !== content) {
|
|
806
|
+
throw new Error('Immutable Codex profile artifact content mismatch');
|
|
807
|
+
}
|
|
808
|
+
await (0, promises_1.chmod)(path, mode);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
if (error.code !== 'ENOENT')
|
|
813
|
+
throw error;
|
|
814
|
+
}
|
|
815
|
+
const temporaryPath = `${path}.tmp-${process.pid}-${(0, crypto_1.randomUUID)()}`;
|
|
816
|
+
try {
|
|
817
|
+
await (0, promises_1.writeFile)(temporaryPath, content, {
|
|
818
|
+
encoding: 'utf8',
|
|
819
|
+
mode,
|
|
820
|
+
flag: 'wx',
|
|
821
|
+
});
|
|
822
|
+
await (0, promises_1.link)(temporaryPath, path).catch(async (error) => {
|
|
823
|
+
if (error.code !== 'EEXIST')
|
|
824
|
+
throw error;
|
|
825
|
+
if ((await (0, promises_1.readFile)(path, 'utf8')) !== content)
|
|
826
|
+
throw error;
|
|
827
|
+
});
|
|
828
|
+
await (0, promises_1.chmod)(path, mode);
|
|
829
|
+
}
|
|
830
|
+
finally {
|
|
831
|
+
await (0, promises_1.unlink)(temporaryPath).catch(() => undefined);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
exports.CodexPluginProfileMaterializerService = CodexPluginProfileMaterializerService;
|
|
836
|
+
exports.CodexPluginProfileMaterializerService = CodexPluginProfileMaterializerService = __decorate([
|
|
837
|
+
(0, common_1.Injectable)(),
|
|
838
|
+
__param(0, (0, common_1.Optional)()),
|
|
839
|
+
__param(0, (0, common_1.Inject)(exports.CODEX_PLUGIN_PROFILE_ROOT)),
|
|
840
|
+
__metadata("design:paramtypes", [String])
|
|
841
|
+
], CodexPluginProfileMaterializerService);
|
|
842
|
+
//# sourceMappingURL=codex-plugin-profile-materializer.service.js.map
|