@opentag/local-runtime 0.2.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/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/chunk-3GVYPT5P.js +214 -0
- package/dist/chunk-3GVYPT5P.js.map +1 -0
- package/dist/chunk-CUP3SU3F.js +68 -0
- package/dist/chunk-CUP3SU3F.js.map +1 -0
- package/dist/chunk-EKMUVNR7.js +122 -0
- package/dist/chunk-EKMUVNR7.js.map +1 -0
- package/dist/chunk-UAMAECYE.js +255 -0
- package/dist/chunk-UAMAECYE.js.map +1 -0
- package/dist/chunk-UUQUUYQM.js +75 -0
- package/dist/chunk-UUQUUYQM.js.map +1 -0
- package/dist/chunk-ZXR3PYD7.js +168 -0
- package/dist/chunk-ZXR3PYD7.js.map +1 -0
- package/dist/config.d.ts +365 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon.d.ts +42 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +14 -0
- package/dist/daemon.js.map +1 -0
- package/dist/dispatcher.d.ts +24 -0
- package/dist/dispatcher.d.ts.map +1 -0
- package/dist/dispatcher.js +9 -0
- package/dist/dispatcher.js.map +1 -0
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +12 -0
- package/dist/doctor.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/pr.d.ts +19 -0
- package/dist/pr.d.ts.map +1 -0
- package/dist/pr.js +7 -0
- package/dist/pr.js.map +1 -0
- package/dist/runtime.d.ts +37 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +15 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
var ExecutorSchema = z.enum(["echo", "codex", "claude-code"]);
|
|
5
|
+
var KeepWorktreeSchema = z.enum(["always", "on_failure", "never"]);
|
|
6
|
+
var PositiveIntegerSchema = z.number().int().positive();
|
|
7
|
+
var ClaudeCodeExecutorConfigSchema = z.object({
|
|
8
|
+
command: z.string().min(1).optional(),
|
|
9
|
+
model: z.string().min(1).optional(),
|
|
10
|
+
permissionMode: z.enum(["acceptEdits", "auto", "bypassPermissions", "default", "plan"]).optional(),
|
|
11
|
+
dangerouslySkipPermissions: z.boolean().optional()
|
|
12
|
+
});
|
|
13
|
+
var RunnerSecurityPolicySchema = z.object({
|
|
14
|
+
mode: z.enum(["enforce", "audit", "off"]).optional(),
|
|
15
|
+
allowedWorkspaceRoot: z.string().min(1).optional(),
|
|
16
|
+
allowUnsafePrompts: z.boolean().optional(),
|
|
17
|
+
extraSafeEnv: z.array(z.string().min(1)).optional()
|
|
18
|
+
});
|
|
19
|
+
var RepositoryBindingConfigSchema = z.object({
|
|
20
|
+
provider: z.string().min(1).default("github"),
|
|
21
|
+
owner: z.string().min(1),
|
|
22
|
+
repo: z.string().min(1),
|
|
23
|
+
checkoutPath: z.string().min(1),
|
|
24
|
+
defaultExecutor: ExecutorSchema.default("echo"),
|
|
25
|
+
baseBranch: z.string().min(1).default("main"),
|
|
26
|
+
pushRemote: z.string().min(1).default("origin"),
|
|
27
|
+
worktreeRoot: z.string().min(1).optional(),
|
|
28
|
+
keepWorktree: KeepWorktreeSchema.default("on_failure")
|
|
29
|
+
});
|
|
30
|
+
var SlackChannelBindingConfigSchema = z.object({
|
|
31
|
+
teamId: z.string().min(1),
|
|
32
|
+
channelId: z.string().min(1),
|
|
33
|
+
repoProvider: z.string().min(1).default("github"),
|
|
34
|
+
owner: z.string().min(1),
|
|
35
|
+
repo: z.string().min(1)
|
|
36
|
+
});
|
|
37
|
+
var ChannelBindingConfigSchema = z.object({
|
|
38
|
+
provider: z.string().min(1),
|
|
39
|
+
accountId: z.string().min(1),
|
|
40
|
+
conversationId: z.string().min(1),
|
|
41
|
+
repoProvider: z.string().min(1).default("github"),
|
|
42
|
+
owner: z.string().min(1),
|
|
43
|
+
repo: z.string().min(1),
|
|
44
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
45
|
+
});
|
|
46
|
+
var LarkChannelBindingConfigSchema = z.object({
|
|
47
|
+
tenantKey: z.string().min(1),
|
|
48
|
+
chatId: z.string().min(1),
|
|
49
|
+
repoProvider: z.string().min(1).default("github"),
|
|
50
|
+
owner: z.string().min(1),
|
|
51
|
+
repo: z.string().min(1)
|
|
52
|
+
});
|
|
53
|
+
var OpenTagDaemonConfigSchema = z.object({
|
|
54
|
+
runnerId: z.string().min(1).default("runner_local"),
|
|
55
|
+
dispatcherUrl: z.string().url().default("http://localhost:3030"),
|
|
56
|
+
repositories: z.array(RepositoryBindingConfigSchema).default([]),
|
|
57
|
+
channelBindings: z.array(ChannelBindingConfigSchema).optional(),
|
|
58
|
+
slackChannels: z.array(SlackChannelBindingConfigSchema).optional(),
|
|
59
|
+
larkChannels: z.array(LarkChannelBindingConfigSchema).optional(),
|
|
60
|
+
claudeCode: ClaudeCodeExecutorConfigSchema.optional(),
|
|
61
|
+
security: RunnerSecurityPolicySchema.optional(),
|
|
62
|
+
githubToken: z.string().min(1).optional(),
|
|
63
|
+
preparePullRequestBranch: z.boolean().optional(),
|
|
64
|
+
allowAutoCreatePullRequest: z.boolean().optional(),
|
|
65
|
+
pairingToken: z.string().min(1).optional(),
|
|
66
|
+
pollIntervalMs: PositiveIntegerSchema.default(5e3),
|
|
67
|
+
heartbeatIntervalMs: PositiveIntegerSchema.default(15e3)
|
|
68
|
+
});
|
|
69
|
+
function channelBindingIdentity(binding) {
|
|
70
|
+
return JSON.stringify([binding.provider, binding.accountId, binding.conversationId]);
|
|
71
|
+
}
|
|
72
|
+
function formatChannelBindingIdentity(binding) {
|
|
73
|
+
return `${binding.provider}:${binding.accountId}/${binding.conversationId}`;
|
|
74
|
+
}
|
|
75
|
+
function sameChannelBindingTarget(left, right) {
|
|
76
|
+
return left.repoProvider === right.repoProvider && left.owner === right.owner && left.repo === right.repo;
|
|
77
|
+
}
|
|
78
|
+
function normalizeChannelBindings(config) {
|
|
79
|
+
const bindings = [...config.channelBindings ?? []];
|
|
80
|
+
for (const binding of config.slackChannels ?? []) {
|
|
81
|
+
bindings.push({
|
|
82
|
+
provider: "slack",
|
|
83
|
+
accountId: binding.teamId,
|
|
84
|
+
conversationId: binding.channelId,
|
|
85
|
+
repoProvider: binding.repoProvider,
|
|
86
|
+
owner: binding.owner,
|
|
87
|
+
repo: binding.repo
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const binding of config.larkChannels ?? []) {
|
|
91
|
+
bindings.push({
|
|
92
|
+
provider: "lark",
|
|
93
|
+
accountId: binding.tenantKey,
|
|
94
|
+
conversationId: binding.chatId,
|
|
95
|
+
repoProvider: binding.repoProvider,
|
|
96
|
+
owner: binding.owner,
|
|
97
|
+
repo: binding.repo
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const normalized = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const binding of bindings) {
|
|
102
|
+
const key = channelBindingIdentity(binding);
|
|
103
|
+
const existing = normalized.get(key);
|
|
104
|
+
if (existing && !sameChannelBindingTarget(existing, binding)) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Conflicting channel binding for ${formatChannelBindingIdentity(binding)}: ${existing.repoProvider}:${existing.owner}/${existing.repo} and ${binding.repoProvider}:${binding.owner}/${binding.repo}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (!existing) {
|
|
110
|
+
normalized.set(key, binding);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return [...normalized.values()];
|
|
114
|
+
}
|
|
115
|
+
function parseNumberFromEnv(name) {
|
|
116
|
+
const raw = process.env[name];
|
|
117
|
+
if (!raw) return void 0;
|
|
118
|
+
const parsed = Number(raw);
|
|
119
|
+
return Number.isFinite(parsed) ? parsed : Number.NaN;
|
|
120
|
+
}
|
|
121
|
+
function formatPath(path) {
|
|
122
|
+
return path.length ? path.join(".") : "config";
|
|
123
|
+
}
|
|
124
|
+
function formatConfigError(error) {
|
|
125
|
+
if (!(error instanceof z.ZodError)) {
|
|
126
|
+
return error instanceof Error ? error.message : String(error);
|
|
127
|
+
}
|
|
128
|
+
return error.issues.map((issue) => `${formatPath(issue.path)}: ${issue.message}`).join("\n");
|
|
129
|
+
}
|
|
130
|
+
function parseDaemonConfig(value) {
|
|
131
|
+
const parsed = OpenTagDaemonConfigSchema.parse(value);
|
|
132
|
+
normalizeChannelBindings(parsed);
|
|
133
|
+
return parsed;
|
|
134
|
+
}
|
|
135
|
+
function createInitialConfig(input) {
|
|
136
|
+
return parseDaemonConfig({
|
|
137
|
+
runnerId: input.runnerId ?? "runner_local",
|
|
138
|
+
dispatcherUrl: input.dispatcherUrl ?? "http://localhost:3030",
|
|
139
|
+
...input.pairingToken ? { pairingToken: input.pairingToken } : {},
|
|
140
|
+
repositories: [
|
|
141
|
+
{
|
|
142
|
+
provider: "github",
|
|
143
|
+
owner: input.owner,
|
|
144
|
+
repo: input.repo,
|
|
145
|
+
checkoutPath: input.checkoutPath,
|
|
146
|
+
defaultExecutor: input.executor ?? "echo",
|
|
147
|
+
baseBranch: input.baseBranch ?? "main",
|
|
148
|
+
pushRemote: input.pushRemote ?? "origin",
|
|
149
|
+
...input.worktreeRoot ? { worktreeRoot: input.worktreeRoot } : {},
|
|
150
|
+
keepWorktree: input.keepWorktree ?? "on_failure"
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function claudePermissionModeFromEnv(value) {
|
|
156
|
+
if (!value) return void 0;
|
|
157
|
+
const parsed = ClaudeCodeExecutorConfigSchema.shape.permissionMode.safeParse(value);
|
|
158
|
+
if (!parsed.success) {
|
|
159
|
+
throw new Error(`Invalid OPENTAG_CLAUDE_PERMISSION_MODE: ${value}`);
|
|
160
|
+
}
|
|
161
|
+
return parsed.data;
|
|
162
|
+
}
|
|
163
|
+
function extraSafeEnvFromEnv(value) {
|
|
164
|
+
if (!value) return void 0;
|
|
165
|
+
const names = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
166
|
+
return names.length > 0 ? names : void 0;
|
|
167
|
+
}
|
|
168
|
+
function loadConfigFromEnv() {
|
|
169
|
+
const configPath = process.env.OPENTAG_CONFIG_PATH;
|
|
170
|
+
if (configPath) {
|
|
171
|
+
return parseDaemonConfig(JSON.parse(readFileSync(configPath, "utf8")));
|
|
172
|
+
}
|
|
173
|
+
const owner = process.env.OPENTAG_REPO_OWNER;
|
|
174
|
+
const repo = process.env.OPENTAG_REPO_NAME;
|
|
175
|
+
const checkoutPath = process.env.OPENTAG_WORKSPACE_PATH;
|
|
176
|
+
const repositoryProvider = process.env.OPENTAG_SLACK_REPO_PROVIDER ?? "github";
|
|
177
|
+
const claudePermissionMode = claudePermissionModeFromEnv(process.env.OPENTAG_CLAUDE_PERMISSION_MODE);
|
|
178
|
+
const repositories = owner && repo && checkoutPath ? [
|
|
179
|
+
{
|
|
180
|
+
provider: repositoryProvider,
|
|
181
|
+
owner,
|
|
182
|
+
repo,
|
|
183
|
+
checkoutPath,
|
|
184
|
+
defaultExecutor: process.env.OPENTAG_DEFAULT_EXECUTOR ?? "echo",
|
|
185
|
+
baseBranch: process.env.OPENTAG_BASE_BRANCH ?? "main",
|
|
186
|
+
pushRemote: process.env.OPENTAG_PUSH_REMOTE ?? "origin",
|
|
187
|
+
...process.env.OPENTAG_WORKTREE_ROOT ? { worktreeRoot: process.env.OPENTAG_WORKTREE_ROOT } : {},
|
|
188
|
+
keepWorktree: process.env.OPENTAG_KEEP_WORKTREE ?? "on_failure"
|
|
189
|
+
}
|
|
190
|
+
] : [];
|
|
191
|
+
const config = {
|
|
192
|
+
runnerId: process.env.OPENTAG_RUNNER_ID ?? "runner_local",
|
|
193
|
+
dispatcherUrl: process.env.OPENTAG_DISPATCHER_URL ?? "http://localhost:3030",
|
|
194
|
+
repositories,
|
|
195
|
+
...process.env.OPENTAG_SLACK_TEAM_ID && process.env.OPENTAG_SLACK_CHANNEL_ID && owner && repo ? {
|
|
196
|
+
slackChannels: [
|
|
197
|
+
{
|
|
198
|
+
teamId: process.env.OPENTAG_SLACK_TEAM_ID,
|
|
199
|
+
channelId: process.env.OPENTAG_SLACK_CHANNEL_ID,
|
|
200
|
+
repoProvider: repositoryProvider,
|
|
201
|
+
owner,
|
|
202
|
+
repo
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
} : {},
|
|
206
|
+
...process.env.OPENTAG_LARK_TENANT_KEY && process.env.OPENTAG_LARK_CHAT_ID && owner && repo ? {
|
|
207
|
+
larkChannels: [
|
|
208
|
+
{
|
|
209
|
+
tenantKey: process.env.OPENTAG_LARK_TENANT_KEY,
|
|
210
|
+
chatId: process.env.OPENTAG_LARK_CHAT_ID,
|
|
211
|
+
repoProvider: repositoryProvider,
|
|
212
|
+
owner,
|
|
213
|
+
repo
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
} : {},
|
|
217
|
+
...process.env.OPENTAG_CLAUDE_COMMAND || process.env.OPENTAG_CLAUDE_MODEL || process.env.OPENTAG_CLAUDE_PERMISSION_MODE || process.env.OPENTAG_CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS ? {
|
|
218
|
+
claudeCode: {
|
|
219
|
+
...process.env.OPENTAG_CLAUDE_COMMAND ? { command: process.env.OPENTAG_CLAUDE_COMMAND } : {},
|
|
220
|
+
...process.env.OPENTAG_CLAUDE_MODEL ? { model: process.env.OPENTAG_CLAUDE_MODEL } : {},
|
|
221
|
+
...claudePermissionMode ? { permissionMode: claudePermissionMode } : {},
|
|
222
|
+
...process.env.OPENTAG_CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS ? { dangerouslySkipPermissions: process.env.OPENTAG_CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS === "true" } : {}
|
|
223
|
+
}
|
|
224
|
+
} : {},
|
|
225
|
+
...process.env.OPENTAG_SECURITY_MODE || process.env.OPENTAG_ALLOWED_WORKSPACE_ROOT || process.env.OPENTAG_ALLOW_UNSAFE_PROMPTS || process.env.OPENTAG_EXTRA_SAFE_ENV ? {
|
|
226
|
+
security: {
|
|
227
|
+
...process.env.OPENTAG_SECURITY_MODE ? { mode: process.env.OPENTAG_SECURITY_MODE } : {},
|
|
228
|
+
...process.env.OPENTAG_ALLOWED_WORKSPACE_ROOT ? { allowedWorkspaceRoot: process.env.OPENTAG_ALLOWED_WORKSPACE_ROOT } : {},
|
|
229
|
+
...process.env.OPENTAG_ALLOW_UNSAFE_PROMPTS ? { allowUnsafePrompts: process.env.OPENTAG_ALLOW_UNSAFE_PROMPTS === "true" } : {},
|
|
230
|
+
...extraSafeEnvFromEnv(process.env.OPENTAG_EXTRA_SAFE_ENV) ? { extraSafeEnv: extraSafeEnvFromEnv(process.env.OPENTAG_EXTRA_SAFE_ENV) } : {}
|
|
231
|
+
}
|
|
232
|
+
} : {},
|
|
233
|
+
...process.env.OPENTAG_GITHUB_TOKEN ? { githubToken: process.env.OPENTAG_GITHUB_TOKEN } : {},
|
|
234
|
+
...process.env.OPENTAG_PREPARE_PR_BRANCH ? { preparePullRequestBranch: process.env.OPENTAG_PREPARE_PR_BRANCH === "true" } : {},
|
|
235
|
+
...process.env.OPENTAG_ALLOW_AUTO_CREATE_PR ? { allowAutoCreatePullRequest: process.env.OPENTAG_ALLOW_AUTO_CREATE_PR === "true" } : {},
|
|
236
|
+
...process.env.OPENTAG_PAIRING_TOKEN ? { pairingToken: process.env.OPENTAG_PAIRING_TOKEN } : {},
|
|
237
|
+
...process.env.OPENTAG_POLL_INTERVAL_MS ? { pollIntervalMs: parseNumberFromEnv("OPENTAG_POLL_INTERVAL_MS") } : {},
|
|
238
|
+
...process.env.OPENTAG_HEARTBEAT_INTERVAL_MS ? { heartbeatIntervalMs: parseNumberFromEnv("OPENTAG_HEARTBEAT_INTERVAL_MS") } : {}
|
|
239
|
+
};
|
|
240
|
+
return parseDaemonConfig(config);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export {
|
|
244
|
+
RepositoryBindingConfigSchema,
|
|
245
|
+
SlackChannelBindingConfigSchema,
|
|
246
|
+
ChannelBindingConfigSchema,
|
|
247
|
+
LarkChannelBindingConfigSchema,
|
|
248
|
+
OpenTagDaemonConfigSchema,
|
|
249
|
+
normalizeChannelBindings,
|
|
250
|
+
formatConfigError,
|
|
251
|
+
parseDaemonConfig,
|
|
252
|
+
createInitialConfig,
|
|
253
|
+
loadConfigFromEnv
|
|
254
|
+
};
|
|
255
|
+
//# sourceMappingURL=chunk-UAMAECYE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { z } from \"zod\";\n\nconst ExecutorSchema = z.enum([\"echo\", \"codex\", \"claude-code\"]);\nconst KeepWorktreeSchema = z.enum([\"always\", \"on_failure\", \"never\"]);\nconst PositiveIntegerSchema = z.number().int().positive();\n\nconst ClaudeCodeExecutorConfigSchema = z.object({\n command: z.string().min(1).optional(),\n model: z.string().min(1).optional(),\n permissionMode: z.enum([\"acceptEdits\", \"auto\", \"bypassPermissions\", \"default\", \"plan\"]).optional(),\n dangerouslySkipPermissions: z.boolean().optional()\n});\n\nconst RunnerSecurityPolicySchema = z.object({\n mode: z.enum([\"enforce\", \"audit\", \"off\"]).optional(),\n allowedWorkspaceRoot: z.string().min(1).optional(),\n allowUnsafePrompts: z.boolean().optional(),\n extraSafeEnv: z.array(z.string().min(1)).optional()\n});\n\nexport const RepositoryBindingConfigSchema = z.object({\n provider: z.string().min(1).default(\"github\"),\n owner: z.string().min(1),\n repo: z.string().min(1),\n checkoutPath: z.string().min(1),\n defaultExecutor: ExecutorSchema.default(\"echo\"),\n baseBranch: z.string().min(1).default(\"main\"),\n pushRemote: z.string().min(1).default(\"origin\"),\n worktreeRoot: z.string().min(1).optional(),\n keepWorktree: KeepWorktreeSchema.default(\"on_failure\")\n});\n\nexport const SlackChannelBindingConfigSchema = z.object({\n teamId: z.string().min(1),\n channelId: z.string().min(1),\n repoProvider: z.string().min(1).default(\"github\"),\n owner: z.string().min(1),\n repo: z.string().min(1)\n});\n\nexport const ChannelBindingConfigSchema = z.object({\n provider: z.string().min(1),\n accountId: z.string().min(1),\n conversationId: z.string().min(1),\n repoProvider: z.string().min(1).default(\"github\"),\n owner: z.string().min(1),\n repo: z.string().min(1),\n metadata: z.record(z.string(), z.unknown()).optional()\n});\n\nexport const LarkChannelBindingConfigSchema = z.object({\n tenantKey: z.string().min(1),\n chatId: z.string().min(1),\n repoProvider: z.string().min(1).default(\"github\"),\n owner: z.string().min(1),\n repo: z.string().min(1)\n});\n\nexport const OpenTagDaemonConfigSchema = z.object({\n runnerId: z.string().min(1).default(\"runner_local\"),\n dispatcherUrl: z.string().url().default(\"http://localhost:3030\"),\n repositories: z.array(RepositoryBindingConfigSchema).default([]),\n channelBindings: z.array(ChannelBindingConfigSchema).optional(),\n slackChannels: z.array(SlackChannelBindingConfigSchema).optional(),\n larkChannels: z.array(LarkChannelBindingConfigSchema).optional(),\n claudeCode: ClaudeCodeExecutorConfigSchema.optional(),\n security: RunnerSecurityPolicySchema.optional(),\n githubToken: z.string().min(1).optional(),\n preparePullRequestBranch: z.boolean().optional(),\n allowAutoCreatePullRequest: z.boolean().optional(),\n pairingToken: z.string().min(1).optional(),\n pollIntervalMs: PositiveIntegerSchema.default(5000),\n heartbeatIntervalMs: PositiveIntegerSchema.default(15000)\n});\n\nexport type RepositoryBindingConfig = z.infer<typeof RepositoryBindingConfigSchema>;\nexport type ChannelBindingConfig = z.infer<typeof ChannelBindingConfigSchema>;\nexport type SlackChannelBindingConfig = z.infer<typeof SlackChannelBindingConfigSchema>;\nexport type LarkChannelBindingConfig = z.infer<typeof LarkChannelBindingConfigSchema>;\nexport type OpenTagDaemonConfig = z.infer<typeof OpenTagDaemonConfigSchema>;\n\nfunction channelBindingIdentity(binding: Pick<ChannelBindingConfig, \"provider\" | \"accountId\" | \"conversationId\">): string {\n return JSON.stringify([binding.provider, binding.accountId, binding.conversationId]);\n}\n\nfunction formatChannelBindingIdentity(binding: Pick<ChannelBindingConfig, \"provider\" | \"accountId\" | \"conversationId\">): string {\n return `${binding.provider}:${binding.accountId}/${binding.conversationId}`;\n}\n\nfunction sameChannelBindingTarget(left: ChannelBindingConfig, right: ChannelBindingConfig): boolean {\n return left.repoProvider === right.repoProvider && left.owner === right.owner && left.repo === right.repo;\n}\n\nexport function normalizeChannelBindings(config: OpenTagDaemonConfig): ChannelBindingConfig[] {\n const bindings: ChannelBindingConfig[] = [...(config.channelBindings ?? [])];\n\n for (const binding of config.slackChannels ?? []) {\n bindings.push({\n provider: \"slack\",\n accountId: binding.teamId,\n conversationId: binding.channelId,\n repoProvider: binding.repoProvider,\n owner: binding.owner,\n repo: binding.repo\n });\n }\n\n for (const binding of config.larkChannels ?? []) {\n bindings.push({\n provider: \"lark\",\n accountId: binding.tenantKey,\n conversationId: binding.chatId,\n repoProvider: binding.repoProvider,\n owner: binding.owner,\n repo: binding.repo\n });\n }\n\n const normalized = new Map<string, ChannelBindingConfig>();\n for (const binding of bindings) {\n const key = channelBindingIdentity(binding);\n const existing = normalized.get(key);\n if (existing && !sameChannelBindingTarget(existing, binding)) {\n throw new Error(\n `Conflicting channel binding for ${formatChannelBindingIdentity(binding)}: ${existing.repoProvider}:${existing.owner}/${existing.repo} and ${binding.repoProvider}:${binding.owner}/${binding.repo}`\n );\n }\n if (!existing) {\n normalized.set(key, binding);\n }\n }\n\n return [...normalized.values()];\n}\n\nexport type InitConfigInput = {\n runnerId?: string;\n dispatcherUrl?: string;\n pairingToken?: string;\n owner: string;\n repo: string;\n checkoutPath: string;\n executor?: string;\n baseBranch?: string;\n pushRemote?: string;\n worktreeRoot?: string;\n keepWorktree?: string;\n};\n\nfunction parseNumberFromEnv(name: string): number | undefined {\n const raw = process.env[name];\n if (!raw) return undefined;\n const parsed = Number(raw);\n return Number.isFinite(parsed) ? parsed : Number.NaN;\n}\n\nfunction formatPath(path: Array<string | number>): string {\n return path.length ? path.join(\".\") : \"config\";\n}\n\nexport function formatConfigError(error: unknown): string {\n if (!(error instanceof z.ZodError)) {\n return error instanceof Error ? error.message : String(error);\n }\n\n return error.issues.map((issue) => `${formatPath(issue.path)}: ${issue.message}`).join(\"\\n\");\n}\n\nexport function parseDaemonConfig(value: unknown): OpenTagDaemonConfig {\n const parsed = OpenTagDaemonConfigSchema.parse(value);\n normalizeChannelBindings(parsed);\n return parsed;\n}\n\nexport function createInitialConfig(input: InitConfigInput): OpenTagDaemonConfig {\n return parseDaemonConfig({\n runnerId: input.runnerId ?? \"runner_local\",\n dispatcherUrl: input.dispatcherUrl ?? \"http://localhost:3030\",\n ...(input.pairingToken ? { pairingToken: input.pairingToken } : {}),\n repositories: [\n {\n provider: \"github\",\n owner: input.owner,\n repo: input.repo,\n checkoutPath: input.checkoutPath,\n defaultExecutor: input.executor ?? \"echo\",\n baseBranch: input.baseBranch ?? \"main\",\n pushRemote: input.pushRemote ?? \"origin\",\n ...(input.worktreeRoot ? { worktreeRoot: input.worktreeRoot } : {}),\n keepWorktree: input.keepWorktree ?? \"on_failure\"\n }\n ]\n });\n}\n\nfunction claudePermissionModeFromEnv(value: string | undefined) {\n if (!value) return undefined;\n const parsed = ClaudeCodeExecutorConfigSchema.shape.permissionMode.safeParse(value);\n if (!parsed.success) {\n throw new Error(`Invalid OPENTAG_CLAUDE_PERMISSION_MODE: ${value}`);\n }\n return parsed.data;\n}\n\nfunction extraSafeEnvFromEnv(value: string | undefined): string[] | undefined {\n if (!value) return undefined;\n const names = value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0);\n return names.length > 0 ? names : undefined;\n}\n\nexport function loadConfigFromEnv(): OpenTagDaemonConfig {\n const configPath = process.env.OPENTAG_CONFIG_PATH;\n if (configPath) {\n return parseDaemonConfig(JSON.parse(readFileSync(configPath, \"utf8\")));\n }\n\n const owner = process.env.OPENTAG_REPO_OWNER;\n const repo = process.env.OPENTAG_REPO_NAME;\n const checkoutPath = process.env.OPENTAG_WORKSPACE_PATH;\n const repositoryProvider = process.env.OPENTAG_SLACK_REPO_PROVIDER ?? \"github\";\n const claudePermissionMode = claudePermissionModeFromEnv(process.env.OPENTAG_CLAUDE_PERMISSION_MODE);\n const repositories =\n owner && repo && checkoutPath\n ? [\n {\n provider: repositoryProvider,\n owner,\n repo,\n checkoutPath,\n defaultExecutor: process.env.OPENTAG_DEFAULT_EXECUTOR ?? \"echo\",\n baseBranch: process.env.OPENTAG_BASE_BRANCH ?? \"main\",\n pushRemote: process.env.OPENTAG_PUSH_REMOTE ?? \"origin\",\n ...(process.env.OPENTAG_WORKTREE_ROOT ? { worktreeRoot: process.env.OPENTAG_WORKTREE_ROOT } : {}),\n keepWorktree: process.env.OPENTAG_KEEP_WORKTREE ?? \"on_failure\"\n }\n ]\n : [];\n\n const config = {\n runnerId: process.env.OPENTAG_RUNNER_ID ?? \"runner_local\",\n dispatcherUrl: process.env.OPENTAG_DISPATCHER_URL ?? \"http://localhost:3030\",\n repositories,\n ...(process.env.OPENTAG_SLACK_TEAM_ID && process.env.OPENTAG_SLACK_CHANNEL_ID && owner && repo\n ? {\n slackChannels: [\n {\n teamId: process.env.OPENTAG_SLACK_TEAM_ID,\n channelId: process.env.OPENTAG_SLACK_CHANNEL_ID,\n repoProvider: repositoryProvider,\n owner,\n repo\n }\n ]\n }\n : {}),\n ...(process.env.OPENTAG_LARK_TENANT_KEY && process.env.OPENTAG_LARK_CHAT_ID && owner && repo\n ? {\n larkChannels: [\n {\n tenantKey: process.env.OPENTAG_LARK_TENANT_KEY,\n chatId: process.env.OPENTAG_LARK_CHAT_ID,\n repoProvider: repositoryProvider,\n owner,\n repo\n }\n ]\n }\n : {}),\n ...(process.env.OPENTAG_CLAUDE_COMMAND ||\n process.env.OPENTAG_CLAUDE_MODEL ||\n process.env.OPENTAG_CLAUDE_PERMISSION_MODE ||\n process.env.OPENTAG_CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS\n ? {\n claudeCode: {\n ...(process.env.OPENTAG_CLAUDE_COMMAND ? { command: process.env.OPENTAG_CLAUDE_COMMAND } : {}),\n ...(process.env.OPENTAG_CLAUDE_MODEL ? { model: process.env.OPENTAG_CLAUDE_MODEL } : {}),\n ...(claudePermissionMode ? { permissionMode: claudePermissionMode } : {}),\n ...(process.env.OPENTAG_CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS\n ? { dangerouslySkipPermissions: process.env.OPENTAG_CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS === \"true\" }\n : {})\n }\n }\n : {}),\n ...(process.env.OPENTAG_SECURITY_MODE ||\n process.env.OPENTAG_ALLOWED_WORKSPACE_ROOT ||\n process.env.OPENTAG_ALLOW_UNSAFE_PROMPTS ||\n process.env.OPENTAG_EXTRA_SAFE_ENV\n ? {\n security: {\n ...(process.env.OPENTAG_SECURITY_MODE\n ? { mode: process.env.OPENTAG_SECURITY_MODE as \"enforce\" | \"audit\" | \"off\" }\n : {}),\n ...(process.env.OPENTAG_ALLOWED_WORKSPACE_ROOT\n ? { allowedWorkspaceRoot: process.env.OPENTAG_ALLOWED_WORKSPACE_ROOT }\n : {}),\n ...(process.env.OPENTAG_ALLOW_UNSAFE_PROMPTS\n ? { allowUnsafePrompts: process.env.OPENTAG_ALLOW_UNSAFE_PROMPTS === \"true\" }\n : {}),\n ...(extraSafeEnvFromEnv(process.env.OPENTAG_EXTRA_SAFE_ENV)\n ? { extraSafeEnv: extraSafeEnvFromEnv(process.env.OPENTAG_EXTRA_SAFE_ENV) }\n : {})\n }\n }\n : {}),\n ...(process.env.OPENTAG_GITHUB_TOKEN ? { githubToken: process.env.OPENTAG_GITHUB_TOKEN } : {}),\n ...(process.env.OPENTAG_PREPARE_PR_BRANCH ? { preparePullRequestBranch: process.env.OPENTAG_PREPARE_PR_BRANCH === \"true\" } : {}),\n ...(process.env.OPENTAG_ALLOW_AUTO_CREATE_PR ? { allowAutoCreatePullRequest: process.env.OPENTAG_ALLOW_AUTO_CREATE_PR === \"true\" } : {}),\n ...(process.env.OPENTAG_PAIRING_TOKEN ? { pairingToken: process.env.OPENTAG_PAIRING_TOKEN } : {}),\n ...(process.env.OPENTAG_POLL_INTERVAL_MS ? { pollIntervalMs: parseNumberFromEnv(\"OPENTAG_POLL_INTERVAL_MS\") } : {}),\n ...(process.env.OPENTAG_HEARTBEAT_INTERVAL_MS\n ? { heartbeatIntervalMs: parseNumberFromEnv(\"OPENTAG_HEARTBEAT_INTERVAL_MS\") }\n : {})\n };\n return parseDaemonConfig(config);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/pr.ts
|
|
2
|
+
import { buildPullRequestBody, createPullRequestViaFetch } from "@opentag/github";
|
|
3
|
+
import { branchNameForRun, commitChangedFiles, nodeCommandRunner, pushBranch } from "@opentag/runner";
|
|
4
|
+
function hasPermission(event, scope) {
|
|
5
|
+
return event.permissions.some((permission) => permission.scope === scope);
|
|
6
|
+
}
|
|
7
|
+
function isGitHubRepositoryTarget(input) {
|
|
8
|
+
const repoProvider = input.event.metadata["repoProvider"];
|
|
9
|
+
return input.binding.provider === "github" && (repoProvider == null || repoProvider === "github");
|
|
10
|
+
}
|
|
11
|
+
function repositoryTargetMatchesBinding(input) {
|
|
12
|
+
const owner = input.event.metadata["owner"];
|
|
13
|
+
const repo = input.event.metadata["repo"];
|
|
14
|
+
if (typeof owner !== "string" || typeof repo !== "string") return false;
|
|
15
|
+
return owner === input.binding.owner && repo === input.binding.repo;
|
|
16
|
+
}
|
|
17
|
+
async function maybeCreatePullRequest(input) {
|
|
18
|
+
if (!input.options.allowAutoCreatePullRequest && !input.options.preparePullRequestBranch) return input.result;
|
|
19
|
+
if (!isGitHubRepositoryTarget({ event: input.event, binding: input.binding })) return input.result;
|
|
20
|
+
if (!repositoryTargetMatchesBinding({ event: input.event, binding: input.binding })) return input.result;
|
|
21
|
+
if (!hasPermission(input.event, "pr:create")) return input.result;
|
|
22
|
+
const changedFiles = input.result.changedFiles ?? [];
|
|
23
|
+
if (changedFiles.length === 0) return input.result;
|
|
24
|
+
const owner = input.binding.owner;
|
|
25
|
+
const repo = input.binding.repo;
|
|
26
|
+
const branchName = branchNameForRun(input.run.id);
|
|
27
|
+
const runner = input.options.commandRunner ?? nodeCommandRunner;
|
|
28
|
+
if (input.run.executor !== "codex") {
|
|
29
|
+
await commitChangedFiles({
|
|
30
|
+
runner,
|
|
31
|
+
workspacePath: input.binding.checkoutPath,
|
|
32
|
+
files: changedFiles,
|
|
33
|
+
message: `OpenTag run ${input.run.id}`
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
await pushBranch({
|
|
37
|
+
runner,
|
|
38
|
+
workspacePath: input.binding.checkoutPath,
|
|
39
|
+
remote: input.binding.pushRemote ?? "origin",
|
|
40
|
+
branchName
|
|
41
|
+
});
|
|
42
|
+
if (!input.options.allowAutoCreatePullRequest) {
|
|
43
|
+
return input.result;
|
|
44
|
+
}
|
|
45
|
+
if (!input.options.githubToken) return input.result;
|
|
46
|
+
const pullRequestUrl = await createPullRequestViaFetch(
|
|
47
|
+
{
|
|
48
|
+
token: input.options.githubToken,
|
|
49
|
+
owner,
|
|
50
|
+
repo,
|
|
51
|
+
title: `OpenTag run ${input.run.id}`,
|
|
52
|
+
body: buildPullRequestBody(input.result),
|
|
53
|
+
head: branchName,
|
|
54
|
+
base: input.binding.baseBranch ?? "main"
|
|
55
|
+
},
|
|
56
|
+
input.options.fetchImpl
|
|
57
|
+
);
|
|
58
|
+
return {
|
|
59
|
+
...input.result,
|
|
60
|
+
createdPullRequestUrl: pullRequestUrl,
|
|
61
|
+
artifacts: [...input.result.artifacts ?? [], { kind: "pull_request", title: "Pull request", uri: pullRequestUrl }],
|
|
62
|
+
nextAction: {
|
|
63
|
+
summary: `Review pull request: ${pullRequestUrl}`,
|
|
64
|
+
hint: {
|
|
65
|
+
kind: "request_review",
|
|
66
|
+
metadata: { pullRequestUrl }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
maybeCreatePullRequest
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=chunk-UUQUUYQM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pr.ts"],"sourcesContent":["import type { OpenTagEvent, OpenTagRun, OpenTagRunResult } from \"@opentag/core\";\nimport { buildPullRequestBody, createPullRequestViaFetch, type FetchLike } from \"@opentag/github\";\nimport { branchNameForRun, commitChangedFiles, nodeCommandRunner, pushBranch, type CommandRunner } from \"@opentag/runner\";\nimport type { RepositoryBindingConfig } from \"./config.js\";\n\nexport type PullRequestOptions = {\n githubToken?: string;\n preparePullRequestBranch?: boolean;\n allowAutoCreatePullRequest?: boolean;\n commandRunner?: CommandRunner;\n fetchImpl?: FetchLike;\n};\n\nfunction hasPermission(event: OpenTagEvent, scope: string): boolean {\n return event.permissions.some((permission) => permission.scope === scope);\n}\n\nfunction isGitHubRepositoryTarget(input: { event: OpenTagEvent; binding: RepositoryBindingConfig }): boolean {\n const repoProvider = input.event.metadata[\"repoProvider\"];\n return input.binding.provider === \"github\" && (repoProvider == null || repoProvider === \"github\");\n}\n\nfunction repositoryTargetMatchesBinding(input: { event: OpenTagEvent; binding: RepositoryBindingConfig }): boolean {\n const owner = input.event.metadata[\"owner\"];\n const repo = input.event.metadata[\"repo\"];\n if (typeof owner !== \"string\" || typeof repo !== \"string\") return false;\n return owner === input.binding.owner && repo === input.binding.repo;\n}\n\nexport async function maybeCreatePullRequest(input: {\n run: OpenTagRun;\n event: OpenTagEvent;\n binding: RepositoryBindingConfig;\n result: OpenTagRunResult;\n options: PullRequestOptions;\n}): Promise<OpenTagRunResult> {\n if (!input.options.allowAutoCreatePullRequest && !input.options.preparePullRequestBranch) return input.result;\n if (!isGitHubRepositoryTarget({ event: input.event, binding: input.binding })) return input.result;\n if (!repositoryTargetMatchesBinding({ event: input.event, binding: input.binding })) return input.result;\n if (!hasPermission(input.event, \"pr:create\")) return input.result;\n const changedFiles = input.result.changedFiles ?? [];\n if (changedFiles.length === 0) return input.result;\n const owner = input.binding.owner;\n const repo = input.binding.repo;\n\n const branchName = branchNameForRun(input.run.id);\n const runner = input.options.commandRunner ?? nodeCommandRunner;\n if (input.run.executor !== \"codex\") {\n await commitChangedFiles({\n runner,\n workspacePath: input.binding.checkoutPath,\n files: changedFiles,\n message: `OpenTag run ${input.run.id}`\n });\n }\n await pushBranch({\n runner,\n workspacePath: input.binding.checkoutPath,\n remote: input.binding.pushRemote ?? \"origin\",\n branchName\n });\n\n if (!input.options.allowAutoCreatePullRequest) {\n return input.result;\n }\n if (!input.options.githubToken) return input.result;\n\n const pullRequestUrl = await createPullRequestViaFetch(\n {\n token: input.options.githubToken,\n owner,\n repo,\n title: `OpenTag run ${input.run.id}`,\n body: buildPullRequestBody(input.result),\n head: branchName,\n base: input.binding.baseBranch ?? \"main\"\n },\n input.options.fetchImpl\n );\n\n return {\n ...input.result,\n createdPullRequestUrl: pullRequestUrl,\n artifacts: [...(input.result.artifacts ?? []), { kind: \"pull_request\", title: \"Pull request\", uri: pullRequestUrl }],\n nextAction: {\n summary: `Review pull request: ${pullRequestUrl}`,\n hint: {\n kind: \"request_review\",\n metadata: { pullRequestUrl }\n }\n }\n };\n}\n"],"mappings":";AACA,SAAS,sBAAsB,iCAAiD;AAChF,SAAS,kBAAkB,oBAAoB,mBAAmB,kBAAsC;AAWxG,SAAS,cAAc,OAAqB,OAAwB;AAClE,SAAO,MAAM,YAAY,KAAK,CAAC,eAAe,WAAW,UAAU,KAAK;AAC1E;AAEA,SAAS,yBAAyB,OAA2E;AAC3G,QAAM,eAAe,MAAM,MAAM,SAAS,cAAc;AACxD,SAAO,MAAM,QAAQ,aAAa,aAAa,gBAAgB,QAAQ,iBAAiB;AAC1F;AAEA,SAAS,+BAA+B,OAA2E;AACjH,QAAM,QAAQ,MAAM,MAAM,SAAS,OAAO;AAC1C,QAAM,OAAO,MAAM,MAAM,SAAS,MAAM;AACxC,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,SAAU,QAAO;AAClE,SAAO,UAAU,MAAM,QAAQ,SAAS,SAAS,MAAM,QAAQ;AACjE;AAEA,eAAsB,uBAAuB,OAMf;AAC5B,MAAI,CAAC,MAAM,QAAQ,8BAA8B,CAAC,MAAM,QAAQ,yBAA0B,QAAO,MAAM;AACvG,MAAI,CAAC,yBAAyB,EAAE,OAAO,MAAM,OAAO,SAAS,MAAM,QAAQ,CAAC,EAAG,QAAO,MAAM;AAC5F,MAAI,CAAC,+BAA+B,EAAE,OAAO,MAAM,OAAO,SAAS,MAAM,QAAQ,CAAC,EAAG,QAAO,MAAM;AAClG,MAAI,CAAC,cAAc,MAAM,OAAO,WAAW,EAAG,QAAO,MAAM;AAC3D,QAAM,eAAe,MAAM,OAAO,gBAAgB,CAAC;AACnD,MAAI,aAAa,WAAW,EAAG,QAAO,MAAM;AAC5C,QAAM,QAAQ,MAAM,QAAQ;AAC5B,QAAM,OAAO,MAAM,QAAQ;AAE3B,QAAM,aAAa,iBAAiB,MAAM,IAAI,EAAE;AAChD,QAAM,SAAS,MAAM,QAAQ,iBAAiB;AAC9C,MAAI,MAAM,IAAI,aAAa,SAAS;AAClC,UAAM,mBAAmB;AAAA,MACvB;AAAA,MACA,eAAe,MAAM,QAAQ;AAAA,MAC7B,OAAO;AAAA,MACP,SAAS,eAAe,MAAM,IAAI,EAAE;AAAA,IACtC,CAAC;AAAA,EACH;AACA,QAAM,WAAW;AAAA,IACf;AAAA,IACA,eAAe,MAAM,QAAQ;AAAA,IAC7B,QAAQ,MAAM,QAAQ,cAAc;AAAA,IACpC;AAAA,EACF,CAAC;AAED,MAAI,CAAC,MAAM,QAAQ,4BAA4B;AAC7C,WAAO,MAAM;AAAA,EACf;AACA,MAAI,CAAC,MAAM,QAAQ,YAAa,QAAO,MAAM;AAE7C,QAAM,iBAAiB,MAAM;AAAA,IAC3B;AAAA,MACE,OAAO,MAAM,QAAQ;AAAA,MACrB;AAAA,MACA;AAAA,MACA,OAAO,eAAe,MAAM,IAAI,EAAE;AAAA,MAClC,MAAM,qBAAqB,MAAM,MAAM;AAAA,MACvC,MAAM;AAAA,MACN,MAAM,MAAM,QAAQ,cAAc;AAAA,IACpC;AAAA,IACA,MAAM,QAAQ;AAAA,EAChB;AAEA,SAAO;AAAA,IACL,GAAG,MAAM;AAAA,IACT,uBAAuB;AAAA,IACvB,WAAW,CAAC,GAAI,MAAM,OAAO,aAAa,CAAC,GAAI,EAAE,MAAM,gBAAgB,OAAO,gBAAgB,KAAK,eAAe,CAAC;AAAA,IACnH,YAAY;AAAA,MACV,SAAS,wBAAwB,cAAc;AAAA,MAC/C,MAAM;AAAA,QACJ,MAAM;AAAA,QACN,UAAU,EAAE,eAAe;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeChannelBindings
|
|
3
|
+
} from "./chunk-UAMAECYE.js";
|
|
4
|
+
|
|
5
|
+
// src/doctor.ts
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { nodeCommandRunner } from "@opentag/runner";
|
|
8
|
+
import { createOpenTagClient } from "@opentag/client";
|
|
9
|
+
function check(status, name, message) {
|
|
10
|
+
return { name, status, message };
|
|
11
|
+
}
|
|
12
|
+
async function checkGitCheckout(input) {
|
|
13
|
+
const checks = [];
|
|
14
|
+
if (!existsSync(input.repository.checkoutPath)) {
|
|
15
|
+
return [check("fail", `${input.repository.owner}/${input.repository.repo} checkout`, `Path does not exist: ${input.repository.checkoutPath}`)];
|
|
16
|
+
}
|
|
17
|
+
checks.push(check("ok", `${input.repository.owner}/${input.repository.repo} checkout`, input.repository.checkoutPath));
|
|
18
|
+
try {
|
|
19
|
+
const gitRepo = await input.commandRunner.run("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
20
|
+
cwd: input.repository.checkoutPath
|
|
21
|
+
});
|
|
22
|
+
if (gitRepo.exitCode !== 0 || gitRepo.stdout.trim() !== "true") {
|
|
23
|
+
checks.push(check("fail", `${input.repository.owner}/${input.repository.repo} git repo`, gitRepo.stderr || gitRepo.stdout || "Not a git repository."));
|
|
24
|
+
return checks;
|
|
25
|
+
}
|
|
26
|
+
checks.push(check("ok", `${input.repository.owner}/${input.repository.repo} git repo`, "Git checkout detected"));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
checks.push(
|
|
29
|
+
check(
|
|
30
|
+
"fail",
|
|
31
|
+
`${input.repository.owner}/${input.repository.repo} git repo`,
|
|
32
|
+
error instanceof Error ? error.message : String(error)
|
|
33
|
+
)
|
|
34
|
+
);
|
|
35
|
+
return checks;
|
|
36
|
+
}
|
|
37
|
+
const executor = input.executor;
|
|
38
|
+
if (!executor) {
|
|
39
|
+
checks.push(check("fail", `${input.repository.defaultExecutor} executor`, "No local executor is configured with this id."));
|
|
40
|
+
return checks;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const readiness = await executor.canRun({
|
|
44
|
+
runId: "doctor",
|
|
45
|
+
workspacePath: input.repository.checkoutPath,
|
|
46
|
+
...input.repository.baseBranch ? { baseBranch: input.repository.baseBranch } : {},
|
|
47
|
+
...input.repository.worktreeRoot ? { worktreeRoot: input.repository.worktreeRoot } : {},
|
|
48
|
+
...input.repository.keepWorktree ? { keepWorktree: input.repository.keepWorktree } : {},
|
|
49
|
+
command: { rawText: "doctor", intent: "unknown", args: {} },
|
|
50
|
+
context: []
|
|
51
|
+
});
|
|
52
|
+
checks.push(
|
|
53
|
+
readiness.ready ? check("ok", `${input.repository.defaultExecutor} executor`, `${executor.displayName} is ready`) : check("fail", `${input.repository.defaultExecutor} executor`, readiness.reason ?? `${executor.displayName} is not ready`)
|
|
54
|
+
);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
checks.push(
|
|
57
|
+
check(
|
|
58
|
+
"fail",
|
|
59
|
+
`${input.repository.defaultExecutor} executor`,
|
|
60
|
+
error instanceof Error ? error.message : String(error)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return checks;
|
|
65
|
+
}
|
|
66
|
+
async function runDoctor(input) {
|
|
67
|
+
const checks = [];
|
|
68
|
+
const commandRunner = input.commandRunner ?? nodeCommandRunner;
|
|
69
|
+
const client = createOpenTagClient({
|
|
70
|
+
dispatcherUrl: input.config.dispatcherUrl,
|
|
71
|
+
...input.config.pairingToken ? { pairingToken: input.config.pairingToken } : {},
|
|
72
|
+
...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
const response = await (input.fetchImpl ?? fetch)(`${input.config.dispatcherUrl.replace(/\/$/, "")}/healthz`);
|
|
76
|
+
checks.push(response.ok ? check("ok", "dispatcher health", input.config.dispatcherUrl) : check("fail", "dispatcher health", `${response.status}`));
|
|
77
|
+
} catch (error) {
|
|
78
|
+
checks.push(check("fail", "dispatcher health", error instanceof Error ? error.message : String(error)));
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const { runner } = await client.getRunner({ runnerId: input.config.runnerId });
|
|
82
|
+
checks.push(check("ok", "runner registration", `${runner.runnerId} (${runner.name})`));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
checks.push(check(message.includes("runner_not_found") ? "fail" : "warn", "runner registration", message));
|
|
86
|
+
}
|
|
87
|
+
if (!input.config.repositories.length) {
|
|
88
|
+
checks.push(check("fail", "repository config", "No repositories are configured."));
|
|
89
|
+
}
|
|
90
|
+
for (const repository of input.config.repositories) {
|
|
91
|
+
checks.push(
|
|
92
|
+
...await checkGitCheckout({
|
|
93
|
+
repository,
|
|
94
|
+
commandRunner,
|
|
95
|
+
...input.executors[repository.defaultExecutor] ? { executor: input.executors[repository.defaultExecutor] } : {}
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
try {
|
|
99
|
+
const { binding } = await client.getRepositoryBinding({
|
|
100
|
+
provider: repository.provider,
|
|
101
|
+
owner: repository.owner,
|
|
102
|
+
repo: repository.repo
|
|
103
|
+
});
|
|
104
|
+
checks.push(
|
|
105
|
+
binding.runnerId === input.config.runnerId ? check("ok", `${repository.owner}/${repository.repo} binding`, `Bound to ${binding.runnerId}`) : check("fail", `${repository.owner}/${repository.repo} binding`, `Bound to ${binding.runnerId}, expected ${input.config.runnerId}`)
|
|
106
|
+
);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
checks.push(check(message.includes("repo_binding_not_found") ? "warn" : "fail", `${repository.owner}/${repository.repo} binding`, message));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const binding of normalizeChannelBindings(input.config)) {
|
|
113
|
+
try {
|
|
114
|
+
const { binding: remoteBinding } = await client.getChannelBinding({
|
|
115
|
+
provider: binding.provider,
|
|
116
|
+
accountId: binding.accountId,
|
|
117
|
+
conversationId: binding.conversationId
|
|
118
|
+
});
|
|
119
|
+
checks.push(
|
|
120
|
+
remoteBinding.repoProvider === binding.repoProvider && remoteBinding.owner === binding.owner && remoteBinding.repo === binding.repo ? check(
|
|
121
|
+
"ok",
|
|
122
|
+
`${binding.provider}:${binding.accountId}/${binding.conversationId} binding`,
|
|
123
|
+
`${remoteBinding.repoProvider}:${remoteBinding.owner}/${remoteBinding.repo}`
|
|
124
|
+
) : check(
|
|
125
|
+
"fail",
|
|
126
|
+
`${binding.provider}:${binding.accountId}/${binding.conversationId} binding`,
|
|
127
|
+
`Bound to ${remoteBinding.repoProvider}:${remoteBinding.owner}/${remoteBinding.repo}, expected ${binding.repoProvider}:${binding.owner}/${binding.repo}`
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
132
|
+
checks.push(
|
|
133
|
+
check(
|
|
134
|
+
message.includes("channel_binding_not_found") ? "warn" : "fail",
|
|
135
|
+
`${binding.provider}:${binding.accountId}/${binding.conversationId} binding`,
|
|
136
|
+
message
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (input.config.allowAutoCreatePullRequest) {
|
|
142
|
+
checks.push(
|
|
143
|
+
input.config.githubToken ? check("ok", "GitHub PR actions", "Configured for legacy immediate PR creation") : check("warn", "GitHub PR actions", "Immediate PR creation is enabled, but githubToken is not configured")
|
|
144
|
+
);
|
|
145
|
+
} else if (input.config.preparePullRequestBranch) {
|
|
146
|
+
checks.push(
|
|
147
|
+
input.config.githubToken ? check("ok", "GitHub PR actions", "Configured for thread-native `apply 1` PR creation") : check("warn", "GitHub PR actions", "Run branches will be pushed, but githubToken is required for `apply 1` PR creation")
|
|
148
|
+
);
|
|
149
|
+
} else if (input.config.githubToken) {
|
|
150
|
+
checks.push(check("warn", "GitHub PR actions", "githubToken is configured, but run branch preparation is disabled"));
|
|
151
|
+
} else {
|
|
152
|
+
checks.push(check("warn", "GitHub PR actions", "Not configured; PR creation actions will be skipped or fail"));
|
|
153
|
+
}
|
|
154
|
+
return checks;
|
|
155
|
+
}
|
|
156
|
+
function formatDoctorChecks(checks) {
|
|
157
|
+
return checks.map((item) => `${item.status.toUpperCase().padEnd(4)} ${item.name}: ${item.message}`).join("\n");
|
|
158
|
+
}
|
|
159
|
+
function doctorHasFailures(checks) {
|
|
160
|
+
return checks.some((item) => item.status === "fail");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
runDoctor,
|
|
165
|
+
formatDoctorChecks,
|
|
166
|
+
doctorHasFailures
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=chunk-ZXR3PYD7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/doctor.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { nodeCommandRunner, type CommandRunner, type ExecutorAdapter } from \"@opentag/runner\";\nimport { createOpenTagClient } from \"@opentag/client\";\nimport { normalizeChannelBindings } from \"./config.js\";\nimport type { OpenTagDaemonConfig, RepositoryBindingConfig } from \"./config.js\";\n\nexport type DoctorCheckStatus = \"ok\" | \"warn\" | \"fail\";\n\nexport type DoctorCheck = {\n name: string;\n status: DoctorCheckStatus;\n message: string;\n};\n\nfunction check(status: DoctorCheckStatus, name: string, message: string): DoctorCheck {\n return { name, status, message };\n}\n\nasync function checkGitCheckout(input: {\n repository: RepositoryBindingConfig;\n executor?: ExecutorAdapter;\n commandRunner: CommandRunner;\n}): Promise<DoctorCheck[]> {\n const checks: DoctorCheck[] = [];\n if (!existsSync(input.repository.checkoutPath)) {\n return [check(\"fail\", `${input.repository.owner}/${input.repository.repo} checkout`, `Path does not exist: ${input.repository.checkoutPath}`)];\n }\n checks.push(check(\"ok\", `${input.repository.owner}/${input.repository.repo} checkout`, input.repository.checkoutPath));\n\n try {\n const gitRepo = await input.commandRunner.run(\"git\", [\"rev-parse\", \"--is-inside-work-tree\"], {\n cwd: input.repository.checkoutPath\n });\n if (gitRepo.exitCode !== 0 || gitRepo.stdout.trim() !== \"true\") {\n checks.push(check(\"fail\", `${input.repository.owner}/${input.repository.repo} git repo`, gitRepo.stderr || gitRepo.stdout || \"Not a git repository.\"));\n return checks;\n }\n checks.push(check(\"ok\", `${input.repository.owner}/${input.repository.repo} git repo`, \"Git checkout detected\"));\n } catch (error) {\n checks.push(\n check(\n \"fail\",\n `${input.repository.owner}/${input.repository.repo} git repo`,\n error instanceof Error ? error.message : String(error)\n )\n );\n return checks;\n }\n\n const executor = input.executor;\n if (!executor) {\n checks.push(check(\"fail\", `${input.repository.defaultExecutor} executor`, \"No local executor is configured with this id.\"));\n return checks;\n }\n try {\n const readiness = await executor.canRun({\n runId: \"doctor\",\n workspacePath: input.repository.checkoutPath,\n ...(input.repository.baseBranch ? { baseBranch: input.repository.baseBranch } : {}),\n ...(input.repository.worktreeRoot ? { worktreeRoot: input.repository.worktreeRoot } : {}),\n ...(input.repository.keepWorktree ? { keepWorktree: input.repository.keepWorktree } : {}),\n command: { rawText: \"doctor\", intent: \"unknown\", args: {} },\n context: []\n });\n checks.push(\n readiness.ready\n ? check(\"ok\", `${input.repository.defaultExecutor} executor`, `${executor.displayName} is ready`)\n : check(\"fail\", `${input.repository.defaultExecutor} executor`, readiness.reason ?? `${executor.displayName} is not ready`)\n );\n } catch (error) {\n checks.push(\n check(\n \"fail\",\n `${input.repository.defaultExecutor} executor`,\n error instanceof Error ? error.message : String(error)\n )\n );\n }\n return checks;\n}\n\nexport async function runDoctor(input: {\n config: OpenTagDaemonConfig;\n executors: Record<string, ExecutorAdapter>;\n fetchImpl?: typeof fetch;\n commandRunner?: CommandRunner;\n}): Promise<DoctorCheck[]> {\n const checks: DoctorCheck[] = [];\n const commandRunner = input.commandRunner ?? nodeCommandRunner;\n const client = createOpenTagClient({\n dispatcherUrl: input.config.dispatcherUrl,\n ...(input.config.pairingToken ? { pairingToken: input.config.pairingToken } : {}),\n ...(input.fetchImpl ? { fetchImpl: input.fetchImpl } : {})\n });\n\n try {\n const response = await (input.fetchImpl ?? fetch)(`${input.config.dispatcherUrl.replace(/\\/$/, \"\")}/healthz`);\n checks.push(response.ok ? check(\"ok\", \"dispatcher health\", input.config.dispatcherUrl) : check(\"fail\", \"dispatcher health\", `${response.status}`));\n } catch (error) {\n checks.push(check(\"fail\", \"dispatcher health\", error instanceof Error ? error.message : String(error)));\n }\n\n try {\n const { runner } = await client.getRunner({ runnerId: input.config.runnerId });\n checks.push(check(\"ok\", \"runner registration\", `${runner.runnerId} (${runner.name})`));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n checks.push(check(message.includes(\"runner_not_found\") ? \"fail\" : \"warn\", \"runner registration\", message));\n }\n\n if (!input.config.repositories.length) {\n checks.push(check(\"fail\", \"repository config\", \"No repositories are configured.\"));\n }\n\n for (const repository of input.config.repositories) {\n checks.push(\n ...(await checkGitCheckout({\n repository,\n commandRunner,\n ...(input.executors[repository.defaultExecutor] ? { executor: input.executors[repository.defaultExecutor] } : {})\n }))\n );\n\n try {\n const { binding } = await client.getRepositoryBinding({\n provider: repository.provider,\n owner: repository.owner,\n repo: repository.repo\n });\n checks.push(\n binding.runnerId === input.config.runnerId\n ? check(\"ok\", `${repository.owner}/${repository.repo} binding`, `Bound to ${binding.runnerId}`)\n : check(\"fail\", `${repository.owner}/${repository.repo} binding`, `Bound to ${binding.runnerId}, expected ${input.config.runnerId}`)\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n checks.push(check(message.includes(\"repo_binding_not_found\") ? \"warn\" : \"fail\", `${repository.owner}/${repository.repo} binding`, message));\n }\n }\n\n for (const binding of normalizeChannelBindings(input.config)) {\n try {\n const { binding: remoteBinding } = await client.getChannelBinding({\n provider: binding.provider,\n accountId: binding.accountId,\n conversationId: binding.conversationId\n });\n checks.push(\n remoteBinding.repoProvider === binding.repoProvider &&\n remoteBinding.owner === binding.owner &&\n remoteBinding.repo === binding.repo\n ? check(\n \"ok\",\n `${binding.provider}:${binding.accountId}/${binding.conversationId} binding`,\n `${remoteBinding.repoProvider}:${remoteBinding.owner}/${remoteBinding.repo}`\n )\n : check(\n \"fail\",\n `${binding.provider}:${binding.accountId}/${binding.conversationId} binding`,\n `Bound to ${remoteBinding.repoProvider}:${remoteBinding.owner}/${remoteBinding.repo}, expected ${binding.repoProvider}:${binding.owner}/${binding.repo}`\n )\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n checks.push(\n check(\n message.includes(\"channel_binding_not_found\") ? \"warn\" : \"fail\",\n `${binding.provider}:${binding.accountId}/${binding.conversationId} binding`,\n message\n )\n );\n }\n }\n\n if (input.config.allowAutoCreatePullRequest) {\n checks.push(\n input.config.githubToken\n ? check(\"ok\", \"GitHub PR actions\", \"Configured for legacy immediate PR creation\")\n : check(\"warn\", \"GitHub PR actions\", \"Immediate PR creation is enabled, but githubToken is not configured\")\n );\n } else if (input.config.preparePullRequestBranch) {\n checks.push(\n input.config.githubToken\n ? check(\"ok\", \"GitHub PR actions\", \"Configured for thread-native `apply 1` PR creation\")\n : check(\"warn\", \"GitHub PR actions\", \"Run branches will be pushed, but githubToken is required for `apply 1` PR creation\")\n );\n } else if (input.config.githubToken) {\n checks.push(check(\"warn\", \"GitHub PR actions\", \"githubToken is configured, but run branch preparation is disabled\"));\n } else {\n checks.push(check(\"warn\", \"GitHub PR actions\", \"Not configured; PR creation actions will be skipped or fail\"));\n }\n\n return checks;\n}\n\nexport function formatDoctorChecks(checks: DoctorCheck[]): string {\n return checks.map((item) => `${item.status.toUpperCase().padEnd(4)} ${item.name}: ${item.message}`).join(\"\\n\");\n}\n\nexport function doctorHasFailures(checks: DoctorCheck[]): boolean {\n return checks.some((item) => item.status === \"fail\");\n}\n"],"mappings":"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","names":[]}
|