@wayai/cli 0.3.202 → 0.3.204
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2510 -570
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -447,11 +447,11 @@ function captureException2(error, context) {
|
|
|
447
447
|
Sentry.captureException(error);
|
|
448
448
|
});
|
|
449
449
|
}
|
|
450
|
-
function addApiBreadcrumb(method,
|
|
450
|
+
function addApiBreadcrumb(method, path41) {
|
|
451
451
|
if (!initialized) return;
|
|
452
452
|
Sentry.addBreadcrumb({
|
|
453
453
|
category: "http",
|
|
454
|
-
message: `${method} ${
|
|
454
|
+
message: `${method} ${path41}`,
|
|
455
455
|
level: "info"
|
|
456
456
|
});
|
|
457
457
|
}
|
|
@@ -2944,8 +2944,8 @@ var init_parseUtil = __esm({
|
|
|
2944
2944
|
init_errors();
|
|
2945
2945
|
init_en();
|
|
2946
2946
|
makeIssue = (params) => {
|
|
2947
|
-
const { data, path:
|
|
2948
|
-
const fullPath = [...
|
|
2947
|
+
const { data, path: path41, errorMaps, issueData } = params;
|
|
2948
|
+
const fullPath = [...path41, ...issueData.path || []];
|
|
2949
2949
|
const fullIssue = {
|
|
2950
2950
|
...issueData,
|
|
2951
2951
|
path: fullPath
|
|
@@ -3256,11 +3256,11 @@ var init_types = __esm({
|
|
|
3256
3256
|
init_parseUtil();
|
|
3257
3257
|
init_util();
|
|
3258
3258
|
ParseInputLazyPath = class {
|
|
3259
|
-
constructor(parent, value,
|
|
3259
|
+
constructor(parent, value, path41, key) {
|
|
3260
3260
|
this._cachedPath = [];
|
|
3261
3261
|
this.parent = parent;
|
|
3262
3262
|
this.data = value;
|
|
3263
|
-
this._path =
|
|
3263
|
+
this._path = path41;
|
|
3264
3264
|
this._key = key;
|
|
3265
3265
|
}
|
|
3266
3266
|
get path() {
|
|
@@ -6920,7 +6920,7 @@ var init_chunk_T7KQKDET = __esm({
|
|
|
6920
6920
|
}
|
|
6921
6921
|
});
|
|
6922
6922
|
|
|
6923
|
-
// ../../packages/core/dist/chunk-
|
|
6923
|
+
// ../../packages/core/dist/chunk-46WU2TD4.js
|
|
6924
6924
|
function containsKanbanPlaceholders(value) {
|
|
6925
6925
|
if (value == null) return false;
|
|
6926
6926
|
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
@@ -6952,12 +6952,12 @@ function typeMatches(typeField, allowed) {
|
|
|
6952
6952
|
if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
|
|
6953
6953
|
return false;
|
|
6954
6954
|
}
|
|
6955
|
-
function validateSchema(schema,
|
|
6955
|
+
function validateSchema(schema, path41, errors, opts = {}) {
|
|
6956
6956
|
if (typeof schema === "boolean") return;
|
|
6957
6957
|
const depth = opts.depth ?? 0;
|
|
6958
6958
|
if (depth > MAX_SCHEMA_DEPTH) {
|
|
6959
6959
|
errors.push({
|
|
6960
|
-
path:
|
|
6960
|
+
path: path41 || "<root>",
|
|
6961
6961
|
message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH} levels`,
|
|
6962
6962
|
suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
|
|
6963
6963
|
});
|
|
@@ -6965,7 +6965,7 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
6965
6965
|
}
|
|
6966
6966
|
if (!isRecord2(schema)) {
|
|
6967
6967
|
errors.push({
|
|
6968
|
-
path:
|
|
6968
|
+
path: path41,
|
|
6969
6969
|
message: `expected object, got ${schema === null ? "null" : typeof schema}`
|
|
6970
6970
|
});
|
|
6971
6971
|
return;
|
|
@@ -6973,14 +6973,14 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
6973
6973
|
if (opts.isRoot) {
|
|
6974
6974
|
if ("type" in schema && schema.type !== "object") {
|
|
6975
6975
|
errors.push({
|
|
6976
|
-
path:
|
|
6976
|
+
path: path41 ? `${path41}.type` : "type",
|
|
6977
6977
|
message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
|
|
6978
6978
|
suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
|
|
6979
6979
|
});
|
|
6980
6980
|
}
|
|
6981
6981
|
} else if ("type" in schema && !typeMatches(schema.type, ALLOWED_TYPES)) {
|
|
6982
6982
|
errors.push({
|
|
6983
|
-
path: `${
|
|
6983
|
+
path: `${path41}.type`,
|
|
6984
6984
|
message: `type must be one of ${[...ALLOWED_TYPES].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
|
|
6985
6985
|
});
|
|
6986
6986
|
}
|
|
@@ -6990,25 +6990,25 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
6990
6990
|
const isPlaceholder = PLACEHOLDER_TOKENS.includes(e);
|
|
6991
6991
|
const isOutcomePlaceholder = e === "[KANBAN_OUTCOME_VALUES]";
|
|
6992
6992
|
errors.push({
|
|
6993
|
-
path: `${
|
|
6993
|
+
path: `${path41}.enum`,
|
|
6994
6994
|
message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
|
|
6995
6995
|
suggestion: isOutcomePlaceholder ? "Declare `outcomes` on the hub's terminal kanban status so the platform can render the placeholder into a valid array; with none configured the platform drops the parameter instead." : isPlaceholder ? "Set `operation: 'update_kanban_status'` on the tool and ensure at least one hub.kanban_status has `allowsAgentUpdate: true` so the platform can render the placeholder into a valid array." : 'Wrap the value in an array: `enum: ["value"]`.'
|
|
6996
6996
|
});
|
|
6997
6997
|
} else if (!Array.isArray(e)) {
|
|
6998
6998
|
errors.push({
|
|
6999
|
-
path: `${
|
|
6999
|
+
path: `${path41}.enum`,
|
|
7000
7000
|
message: `enum must be a non-empty array of primitives, got ${typeof e}`
|
|
7001
7001
|
});
|
|
7002
7002
|
} else if (e.length === 0) {
|
|
7003
7003
|
errors.push({
|
|
7004
|
-
path: `${
|
|
7004
|
+
path: `${path41}.enum`,
|
|
7005
7005
|
message: "enum must not be empty"
|
|
7006
7006
|
});
|
|
7007
7007
|
} else {
|
|
7008
7008
|
for (let i = 0; i < e.length; i++) {
|
|
7009
7009
|
if (!isPrimitive(e[i])) {
|
|
7010
7010
|
errors.push({
|
|
7011
|
-
path: `${
|
|
7011
|
+
path: `${path41}.enum[${i}]`,
|
|
7012
7012
|
message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
|
|
7013
7013
|
});
|
|
7014
7014
|
}
|
|
@@ -7018,7 +7018,7 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
7018
7018
|
for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
|
|
7019
7019
|
if (key in schema && typeof schema[key] === "boolean") {
|
|
7020
7020
|
errors.push({
|
|
7021
|
-
path: `${
|
|
7021
|
+
path: `${path41}.${key}`,
|
|
7022
7022
|
message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
|
|
7023
7023
|
suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
|
|
7024
7024
|
});
|
|
@@ -7027,45 +7027,45 @@ function validateSchema(schema, path39, errors, opts = {}) {
|
|
|
7027
7027
|
if ("properties" in schema) {
|
|
7028
7028
|
if (!isRecord2(schema.properties)) {
|
|
7029
7029
|
errors.push({
|
|
7030
|
-
path: `${
|
|
7030
|
+
path: `${path41}.properties`,
|
|
7031
7031
|
message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
|
|
7032
7032
|
});
|
|
7033
7033
|
} else {
|
|
7034
7034
|
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
7035
|
-
validateSchema(propSchema, `${
|
|
7035
|
+
validateSchema(propSchema, `${path41}.properties.${propName}`, errors, { depth: depth + 1 });
|
|
7036
7036
|
}
|
|
7037
7037
|
}
|
|
7038
7038
|
}
|
|
7039
7039
|
if (schemaTypeIncludes(schema, "array") && "items" in schema) {
|
|
7040
7040
|
if (Array.isArray(schema.items)) {
|
|
7041
|
-
schema.items.forEach((sub, i) => validateSchema(sub, `${
|
|
7041
|
+
schema.items.forEach((sub, i) => validateSchema(sub, `${path41}.items[${i}]`, errors, { depth: depth + 1 }));
|
|
7042
7042
|
} else {
|
|
7043
|
-
validateSchema(schema.items, `${
|
|
7043
|
+
validateSchema(schema.items, `${path41}.items`, errors, { depth: depth + 1 });
|
|
7044
7044
|
}
|
|
7045
7045
|
}
|
|
7046
7046
|
for (const key of SUBSCHEMA_OBJECT_KEYWORDS) {
|
|
7047
7047
|
if (key in schema && isRecord2(schema[key])) {
|
|
7048
|
-
validateSchema(schema[key], `${
|
|
7048
|
+
validateSchema(schema[key], `${path41}.${key}`, errors, { depth: depth + 1 });
|
|
7049
7049
|
}
|
|
7050
7050
|
}
|
|
7051
7051
|
for (const key of SUBSCHEMA_LIST_KEYWORDS) {
|
|
7052
7052
|
const list = schema[key];
|
|
7053
7053
|
if (Array.isArray(list)) {
|
|
7054
|
-
list.forEach((sub, i) => validateSchema(sub, `${
|
|
7054
|
+
list.forEach((sub, i) => validateSchema(sub, `${path41}.${key}[${i}]`, errors, { depth: depth + 1 }));
|
|
7055
7055
|
}
|
|
7056
7056
|
}
|
|
7057
7057
|
for (const key of SUBSCHEMA_MAP_KEYWORDS) {
|
|
7058
7058
|
const map = schema[key];
|
|
7059
7059
|
if (isRecord2(map)) {
|
|
7060
7060
|
for (const [name, sub] of Object.entries(map)) {
|
|
7061
|
-
validateSchema(sub, `${
|
|
7061
|
+
validateSchema(sub, `${path41}.${key}.${name}`, errors, { depth: depth + 1 });
|
|
7062
7062
|
}
|
|
7063
7063
|
}
|
|
7064
7064
|
}
|
|
7065
7065
|
const reportedPaths = new Set(errors.map((e) => e.path));
|
|
7066
7066
|
for (const [k, v] of Object.entries(schema)) {
|
|
7067
7067
|
if (typeof v !== "string") continue;
|
|
7068
|
-
const fieldPath = `${
|
|
7068
|
+
const fieldPath = `${path41}.${k}`;
|
|
7069
7069
|
if (reportedPaths.has(fieldPath)) continue;
|
|
7070
7070
|
for (const token of PLACEHOLDER_TOKENS) {
|
|
7071
7071
|
if (v === token) {
|
|
@@ -7121,12 +7121,12 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7121
7121
|
const linkTargets = /* @__PURE__ */ new Set();
|
|
7122
7122
|
followups.forEach((followup, i) => {
|
|
7123
7123
|
const ref = followup?.after_followup_id;
|
|
7124
|
-
const
|
|
7124
|
+
const path41 = ["followups", i, "after_followup_id"];
|
|
7125
7125
|
if (followup?.type !== "inactivity_after_before_event") {
|
|
7126
7126
|
if (ref !== void 0) {
|
|
7127
7127
|
ctx.addIssue({
|
|
7128
7128
|
code: external_exports.ZodIssueCode.custom,
|
|
7129
|
-
path:
|
|
7129
|
+
path: path41,
|
|
7130
7130
|
message: `kanban status ${label}: after_followup_id is only valid on inactivity_after_before_event followups (this one is ${followup?.type})`
|
|
7131
7131
|
});
|
|
7132
7132
|
}
|
|
@@ -7135,7 +7135,7 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7135
7135
|
if (ref === void 0) {
|
|
7136
7136
|
ctx.addIssue({
|
|
7137
7137
|
code: external_exports.ZodIssueCode.custom,
|
|
7138
|
-
path:
|
|
7138
|
+
path: path41,
|
|
7139
7139
|
message: `kanban status ${label}: inactivity_after_before_event requires after_followup_id naming the id of a before_event followup in the same status`
|
|
7140
7140
|
});
|
|
7141
7141
|
return;
|
|
@@ -7145,7 +7145,7 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7145
7145
|
if (matches.length > 1) {
|
|
7146
7146
|
ctx.addIssue({
|
|
7147
7147
|
code: external_exports.ZodIssueCode.custom,
|
|
7148
|
-
path:
|
|
7148
|
+
path: path41,
|
|
7149
7149
|
message: `kanban status ${label}: after_followup_id "${ref}" matches ${matches.length} followups \u2014 a link must name exactly one`
|
|
7150
7150
|
});
|
|
7151
7151
|
return;
|
|
@@ -7157,7 +7157,7 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7157
7157
|
);
|
|
7158
7158
|
ctx.addIssue({
|
|
7159
7159
|
code: external_exports.ZodIssueCode.custom,
|
|
7160
|
-
path:
|
|
7160
|
+
path: path41,
|
|
7161
7161
|
message: hasIdlessBeforeEvent ? `kanban status ${label}: after_followup_id "${ref}" matches no followup \u2014 a link target must declare an explicit id, and this status has before_event followups without one` : `kanban status ${label}: after_followup_id "${ref}" matches no followup in this status`
|
|
7162
7162
|
});
|
|
7163
7163
|
return;
|
|
@@ -7165,7 +7165,7 @@ function validateFollowupLinks(followups, label, ctx) {
|
|
|
7165
7165
|
if (target.type !== "before_event") {
|
|
7166
7166
|
ctx.addIssue({
|
|
7167
7167
|
code: external_exports.ZodIssueCode.custom,
|
|
7168
|
-
path:
|
|
7168
|
+
path: path41,
|
|
7169
7169
|
message: `kanban status ${label}: after_followup_id "${ref}" points at a ${target.type} followup \u2014 only before_event followups can anchor a chain`
|
|
7170
7170
|
});
|
|
7171
7171
|
}
|
|
@@ -7421,9 +7421,9 @@ function monitorRuleActions(monitorConfig) {
|
|
|
7421
7421
|
if (!monitorConfig || typeof monitorConfig !== "object") return [];
|
|
7422
7422
|
const config = monitorConfig;
|
|
7423
7423
|
const found = [];
|
|
7424
|
-
const visit = (action,
|
|
7424
|
+
const visit = (action, path41) => {
|
|
7425
7425
|
if (!action || typeof action !== "object" || Array.isArray(action)) return;
|
|
7426
|
-
found.push({ action, path:
|
|
7426
|
+
found.push({ action, path: path41 });
|
|
7427
7427
|
};
|
|
7428
7428
|
if (Array.isArray(config.rules)) {
|
|
7429
7429
|
config.rules.forEach((rule, index) => {
|
|
@@ -7455,25 +7455,25 @@ function collectMonitorRuleIssues(monitorConfig) {
|
|
|
7455
7455
|
const trigger = resolveMonitorTrigger(monitorConfig);
|
|
7456
7456
|
const kinds = monitorRuleActionKinds(trigger);
|
|
7457
7457
|
const issues = [...callUtteranceRuleIssues(monitorConfig), ...automationGateIssues(monitorConfig)];
|
|
7458
|
-
for (const { action, path:
|
|
7459
|
-
if (trigger === "call_utterance" &&
|
|
7458
|
+
for (const { action, path: path41 } of monitorRuleActions(monitorConfig)) {
|
|
7459
|
+
if (trigger === "call_utterance" && path41[0] === "fallback") continue;
|
|
7460
7460
|
const kind = action.kind;
|
|
7461
7461
|
if (typeof kind === "string" && !kinds.includes(kind)) {
|
|
7462
|
-
issues.push({ path: [...
|
|
7462
|
+
issues.push({ path: [...path41, "kind"], message: monitorActionKindMessage(kind, trigger) });
|
|
7463
7463
|
}
|
|
7464
7464
|
const toolName = action.tool_name;
|
|
7465
7465
|
if (kind === "call_tool" && typeof toolName === "string" && isRefusedNativeToolName(toolName, trigger)) {
|
|
7466
|
-
issues.push({ path: [...
|
|
7466
|
+
issues.push({ path: [...path41, "tool_name"], message: monitorRuleToolNotAllowedMessage(toolName, trigger) });
|
|
7467
7467
|
continue;
|
|
7468
7468
|
}
|
|
7469
7469
|
if (kind === "call_tool" && toolName === INSERT_NOTE_TOOL_NAME) {
|
|
7470
|
-
issues.push(...insertNoteArgumentIssues(action,
|
|
7470
|
+
issues.push(...insertNoteArgumentIssues(action, path41));
|
|
7471
7471
|
}
|
|
7472
7472
|
if (kind === "call_tool" && toolName === RUN_MONITOR_TOOL_NAME) {
|
|
7473
7473
|
const callee = readRunMonitorCallee(action);
|
|
7474
7474
|
if (!callee.ok) {
|
|
7475
7475
|
issues.push({
|
|
7476
|
-
path: [...
|
|
7476
|
+
path: [...path41, "args", "monitor_name"],
|
|
7477
7477
|
message: runMonitorCalleeMessage(callee.reason)
|
|
7478
7478
|
});
|
|
7479
7479
|
}
|
|
@@ -7533,11 +7533,11 @@ function insertNoteTemplateMessage(reason) {
|
|
|
7533
7533
|
return `insert_note's template is longer than the ${MONITOR_NOTE_TEMPLATE_MAX}-character limit.`;
|
|
7534
7534
|
}
|
|
7535
7535
|
}
|
|
7536
|
-
function insertNoteArgumentIssues(action,
|
|
7536
|
+
function insertNoteArgumentIssues(action, path41) {
|
|
7537
7537
|
const result = readInsertNoteTemplate(action);
|
|
7538
7538
|
if (result.ok) return [];
|
|
7539
7539
|
return [{
|
|
7540
|
-
path: [...
|
|
7540
|
+
path: [...path41, "args", "template"],
|
|
7541
7541
|
message: insertNoteTemplateMessage(result.reason)
|
|
7542
7542
|
}];
|
|
7543
7543
|
}
|
|
@@ -7634,8 +7634,8 @@ function evalInitialStateError(input) {
|
|
|
7634
7634
|
const parsed = evalInitialStateEntry.safeParse(entries[i]);
|
|
7635
7635
|
if (!parsed.success) {
|
|
7636
7636
|
const issue = parsed.error.issues[0];
|
|
7637
|
-
const
|
|
7638
|
-
return `initial_state[${i}].${
|
|
7637
|
+
const path41 = issue?.path.join(".") || "?";
|
|
7638
|
+
return `initial_state[${i}].${path41} is invalid: ${issue?.message ?? "malformed"}`;
|
|
7639
7639
|
}
|
|
7640
7640
|
if (seenSlugs.has(parsed.data.slug)) {
|
|
7641
7641
|
return `initial_state[${i}].slug "${parsed.data.slug}" is declared more than once`;
|
|
@@ -7859,10 +7859,10 @@ function refineHubAsCodeEvals(config, ctx) {
|
|
|
7859
7859
|
function refineHubAsCodeEvalAttachments(config, ctx) {
|
|
7860
7860
|
const cfg = config;
|
|
7861
7861
|
const referencedHashes = /* @__PURE__ */ new Set();
|
|
7862
|
-
const addTurnIssue = (
|
|
7862
|
+
const addTurnIssue = (path41, turn, name) => {
|
|
7863
7863
|
const error = evalTurnAttachmentsError(turn);
|
|
7864
7864
|
if (error) {
|
|
7865
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path:
|
|
7865
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path41, message: `${name}: ${error}` });
|
|
7866
7866
|
return;
|
|
7867
7867
|
}
|
|
7868
7868
|
for (const hash of collectTurnAttachmentHashes(turn)) referencedHashes.add(hash);
|
|
@@ -7937,17 +7937,17 @@ function refineHubAsCodeCallOpenings(config, ctx) {
|
|
|
7937
7937
|
const agent = entry;
|
|
7938
7938
|
const openings = agent?.call_openings;
|
|
7939
7939
|
if (openings === void 0 || openings === null) return;
|
|
7940
|
-
const
|
|
7940
|
+
const path41 = ["agents", a, "call_openings"];
|
|
7941
7941
|
const name = `agent "${typeof agent?.name === "string" ? agent.name : "<unnamed>"}"`;
|
|
7942
7942
|
if (typeof openings !== "object" || Array.isArray(openings)) {
|
|
7943
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path:
|
|
7943
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path41, message: `${name}: call_openings must map a language to an opening` });
|
|
7944
7944
|
return;
|
|
7945
7945
|
}
|
|
7946
7946
|
if (agent?.role !== "pilot_voice" && Object.keys(openings).length > 0) {
|
|
7947
|
-
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path:
|
|
7947
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path41, message: `${name}: only a pilot_voice agent has call_openings` });
|
|
7948
7948
|
}
|
|
7949
7949
|
for (const [language, raw] of Object.entries(openings)) {
|
|
7950
|
-
const at = [...
|
|
7950
|
+
const at = [...path41, language];
|
|
7951
7951
|
if (!callOpeningLanguage.safeParse(language).success) {
|
|
7952
7952
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: at, message: `${name}: call_openings language must be one of ${CALL_OPENING_LANGUAGES.join(", ")}` });
|
|
7953
7953
|
continue;
|
|
@@ -8074,11 +8074,11 @@ function refineHubAsCodeDelegation(config, ctx) {
|
|
|
8074
8074
|
}
|
|
8075
8075
|
}
|
|
8076
8076
|
if (del.context_boundary === void 0) return;
|
|
8077
|
-
const
|
|
8077
|
+
const path41 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
|
|
8078
8078
|
if (del.type !== "hub") {
|
|
8079
8079
|
ctx.addIssue({
|
|
8080
8080
|
code: external_exports.ZodIssueCode.custom,
|
|
8081
|
-
path:
|
|
8081
|
+
path: path41,
|
|
8082
8082
|
message: `context_boundary applies only to \`type: hub\` delegations (got type "${String(del.type)}")`
|
|
8083
8083
|
});
|
|
8084
8084
|
return;
|
|
@@ -8086,7 +8086,7 @@ function refineHubAsCodeDelegation(config, ctx) {
|
|
|
8086
8086
|
if (!CONTEXT_BOUNDARIES.includes(del.context_boundary)) {
|
|
8087
8087
|
ctx.addIssue({
|
|
8088
8088
|
code: external_exports.ZodIssueCode.custom,
|
|
8089
|
-
path:
|
|
8089
|
+
path: path41,
|
|
8090
8090
|
message: `context_boundary must be one of: ${CONTEXT_BOUNDARIES.join(", ")}`
|
|
8091
8091
|
});
|
|
8092
8092
|
}
|
|
@@ -8230,8 +8230,8 @@ function refineHubAsCodeMonitorRulesOutput(config, ctx) {
|
|
|
8230
8230
|
});
|
|
8231
8231
|
}
|
|
8232
8232
|
var PLACEHOLDER_TOKENS, ALLOWED_TYPES, SUBSCHEMA_OBJECT_KEYWORDS, SUBSCHEMA_LIST_KEYWORDS, SUBSCHEMA_MAP_KEYWORDS, MAX_SCHEMA_DEPTH, uuidSchema, USER_ID_REGEX, timestampSchema, idParamSchema, clientOptionalString, clientOptionalBoolean, clientOptionalNumber, messageAttachment, MAX_MESSAGE_BODY_BYTES, MAX_ATTACHMENTS_PER_MESSAGE, MESSAGE_ENVELOPE_HEADROOM_BYTES, MAX_AUDIO_FILE_BASE64_BYTES, MAX_KANBAN_STATUSES, MAX_FOLLOWUPS_PER_STATUS, MAX_LANES, MAX_ADDITIONAL_CONTEXT_SCHEMA_BYTES, RESERVED_TEMPLATE_DUMP_NAME, kanbanStatusSlugSchema, followupTypeSchema, EVENT_RELATIVE_FOLLOWUP_TYPES, followupTimeUnitSchema, followupIdSchema, SELECTABLE_FOLLOWUP_TYPES, MAX_LINKED_FOLLOWUP_TARGETS_PER_STATUS, followupSchema, MAX_OUTCOMES_PER_STATUS, kanbanOutcomeSchema, EXCLUSIVE_FLAG_PAIRS, kanbanStatusSchema, kanbanStatusesArraySchema, laneSchema, lanesArraySchema, toolIdParam, listToolsQuery, toolAgentQuery, deleteToolQuery, toolConnectionsQuery, nativeToolIdSchema, CONTEXT_BOUNDARIES, contextBoundarySchema, addNativeToolBody, addMcpToolBody, createCustomToolBody, updateCustomToolBody, updateToolExecutionConfigBody, primaryRegionSchema, placementSourceSchema, orgIdParam, orgAdminIdParam, createOrganizationBody, updateOrganizationBody, addOrgAdminBody, HUB_DISPLAY_NAME_MAX_CHARS, HUB_WELCOME_MESSAGE_MAX_CHARS, HUB_STARTER_QUESTIONS_MAX, HUB_STARTER_QUESTION_MAX_CHARS, displayName, welcomeMessage, starterQuestions, hubDisplayNamesSchema, hubWelcomeMessagesSchema, hubStarterQuestionsSchema, hubDisplayNamesField, hubWelcomeMessagesField, hubStarterQuestionsField, HUB_PRESENTATION_FIELDS, HUB_PRESENTATION_SCHEMAS, hubIdParam, hubAdminIdParam, hubUserIdParam, hubIdentityIdParam, hubTeamIdParam, hubTeamUserDeleteParam, hubSchemaQuery, HUB_USERS_PAGE_SIZE_MAX, HUB_USERS_SEARCH_MAX, HUB_USERS_CHANNEL_TYPES, getHubUsersQuery, PREVIEW_LABEL_MAX_LENGTH, MAX_ENDED_INDEX_RETENTION_DAYS, MAX_EVAL_RETENTION_DAYS, MAX_END_USER_DAILY_MESSAGE_LIMIT, endUserDailyMessageLimitField, previewLabelField, hubTagsField, createHubBody, updateHubBody, HUB_TAG_CHANGE_REFUSED, hubTagChangeRefusalSchema, addHubAdminBody, addHubIdentityAuthorizationBody, contactDecisionBody, approveContactBody, listContactAccessQuery, createHubTeamBody, updateHubTeamBody, addHubTeamUserBody, reassignHubUserBody, replicatePreviewBody, HUB_DIFF_SECTION_SET, HUB_DIFF_SECTIONS, updateChannelTestIdentitiesBody, claimCodeChannelParam, claimCodeDeleteParam, PREVIOUS_CONVERSATIONS_MAX, NATIVE_TOOL_NAMES, previousConversationsCountField, summarizationThresholdField, flagConditionSchema, MONITOR_TRIGGERS, monitorTriggerSchema, AUTOMATION_GATE_TRIGGERS, NON_FIRING_TRIGGERS, MONITOR_FIRING_TRIGGERS, MONITOR_DELAY_SECONDS_MIN, MONITOR_IDLE_DELAY_REQUIRED_MESSAGE, MONITOR_HISTORY_MESSAGES_MAX, monitorHistoryMessagesSchema, monitorIncludeToolResultsSchema, MONITOR_INPUT_SHAPING_KEYS, MONITOR_INPUT_SHAPING_SCHEMAS, MONITOR_INPUT_SHAPING_IDLE_MESSAGE, monitorArgumentSourceSchema, MONITOR_NOTE_TEMPLATE_MAX, MONITOR_STEER_NOTE_MAX, MONITOR_GATE_AGENT_NAME_MAX, monitorActionSchema, monitorRuleSchema, MONITOR_RULE_KEYS, MONITOR_RULE_SCHEMAS, MONITOR_RULE_TRIGGERS, MONITOR_RULE_TRIGGER_MESSAGE, CALL_UTTERANCE_SPEAKERS, callUtteranceSpeakerSchema, MONITOR_CALL_KEYS, MONITOR_CALL_SCHEMAS, MONITOR_CALL_KEY_MESSAGE, CALL_STEERING_MIN_CONFIDENCE, CALL_UTTERANCE_RULE_NOT_CONFIDENT_MESSAGE, CALL_UTTERANCE_FALLBACK_MESSAGE, CALL_UTTERANCE_FLAG_CONDITIONS_MESSAGE, CALL_STEER_NOTE_BLANK_MESSAGE, MONITOR_USER_MESSAGE_ACTION_KINDS, MONITOR_ASSISTANT_REPLY_ACTION_KINDS, MONITOR_CALL_UTTERANCE_ACTION_KINDS, MONITOR_AUTOMATION_FIRE_ACTION_KINDS, MONITOR_AUTOMATION_ITEM_ACTION_KINDS, INSERT_NOTE_TOOL_NAME, RUN_MONITOR_TOOL_NAME, MONITOR_RULE_ALLOWED_NATIVE_TOOLS, MONITOR_RULE_REENTRY_TOOLS, MONITOR_RULE_REENTRY_TRACKS, AUTOMATION_FIRE_FLAG_CONDITIONS_MESSAGE, AUTOMATION_FIRE_INPUT_SHAPING_MESSAGE, AUTOMATION_ITEM_FLAG_CONDITIONS_MESSAGE, AUTOMATION_ITEM_INPUT_SHAPING_MESSAGE, AUTOMATION_GATE_KEY_MESSAGES, monitorConfigField, flagConditionsField, agentIdParam, listAgentsQuery, agentConnectionsQuery, createAgentBody, updateAgentBody, AGENT_PARAMETER_TYPES, AGENT_PARAMETER_NAME_REGEX, agentParameterCreateSchema, MAX_AGENT_PARAMETERS_PER_REQUEST, createAgentParametersBody, evalIdParam, sessionIdParam, scenarioSetIdParam, hubIdQuery, evalDateContextSchema, EVAL_FIXTURE_NAME_MAX_LENGTH, evalFixtureOverrideSchema, runSessionQuery, runJourneyQuery, EVAL_INPUT_ROLES, EVAL_INPUT_ROLE_LIST, ROLE_ECHO_MAX, EVAL_INITIAL_STATE_SCOPES, MAX_INITIAL_STATE_ENTRIES, evalInitialStateEntry, evalBody, EVAL_LIST_MAX_LIMIT, evalListLimit, getEvalsQuery, toggleEvalBody, bulkToggleEvalsBody, pacingConfigSchema, EVAL_MAX_SELECTED_SCENARIOS, evalScenarioSelectionEntrySchema, evalScenarioSelectionSchema, sessionConfigSchema, createSessionBody, getSessionsQuery, getSessionResultsQuery, getSessionRunsQuery, evalAnalyticsQuery, getSessionCostQuery, compareSessionsQuery, evalsSqlBody, evalsSqlSchemaQuery, exportResultsQuery, validateEvalBody, hubAgentsQuery, createScenarioSetBody, updateScenarioSetBody, scenarioSetsHubQuery, createScenarioFromConversationBody, createJourneyFromConversationBody, EVAL_ATTACHMENT_HASH_REGEX, evalAttachmentRef, turnMayCarryAttachments, ATTACHMENTS_USER_ONLY_MESSAGE, journeyIdParam, journeyToolCallSchema, journeyTurnSchema, journeyStepOverrideSchema, fixtureField, createJourneyBody, updateJourneyBody, evalSessionConfigResponseSchema, evalSessionResponseSchema, evalSessionListConfigSchema, evalSessionListItemSchema, seedReleaseStatusSchema, EVAL_SESSION_NOT_QUIESCENT, MAX_RESOURCE_FILE_SIZE, CALL_OPENING_LANGUAGES, callOpeningLanguage, CALL_OPENING_MAX_DURATION_MS, CALL_OPENING_MAX_BYTES, CALL_OPENING_CAPTION_MAX_CHARS, CALL_OPENING_CONTENT_TYPES, callOpeningContentType, CALL_OPENING_HASH_REGEX, callOpeningHash, callOpeningCaption, storedCallOpening, MAX_CALL_OPENING_ENCODED_BYTES, MAX_CALL_OPENING_BODY_BYTES, agentCallOpeningParams, putAgentCallOpeningBody, deleteAgentCallOpeningQuery, callOpeningParam, callOpeningQuery, MAX_10MB_BASE64_ENCODED_BYTES, MAX_EVAL_ATTACHMENT_ENCODED_BYTES, MAX_EVAL_ATTACHMENT_CARRIERS, MAX_EVAL_ATTACHMENT_AGGREGATE_ENCODED_BYTES, MAX_RESOURCE_ENCODED_BYTES, MAX_HUB_AS_CODE_RESOURCES, MAX_HUB_AS_CODE_RESOURCE_FILES, MAX_RESOURCE_AGGREGATE_ENCODED_BYTES, MAX_CI_CONFIG_BODY_BYTES, MAX_CALL_OPENING_CARRIERS, MAX_CALL_OPENING_AGGREGATE_ENCODED_BYTES, hubAsCodeStateSchema, hubAsCodeResourceFileSchema, hubAsCodeResourceSchema, uuidPattern, ciUuidSchema, boundedHubAsCodeResourcesSchema, hubAsCodeConfigSchema, ciPullHubIdParam, ciBranchParam, ciPullQuery, ciHubsQuery, ciLookupQuery, ciBranchesQuery, ciSyncBody, ciPushBody, ciDiffBody, ciPublishBody, ciPublishPreviewBody, ciSyncMcpBody, ciOrgResourcesParam, orgResourcesConfigSchema, ciOrgResourcesPushBody, ciOrgResourcesDiffBody, CI_APPLY_ENTITY_KINDS, CI_APPLY_OPERATIONS, ciApplyErrorBaseSchema, ciApplyErrorSchema, ciSyncCountSchema, ciSyncChangesSchema, ciSyncWarningSchema, ciSyncResponseSchema;
|
|
8233
|
-
var
|
|
8234
|
-
"../../packages/core/dist/chunk-
|
|
8233
|
+
var init_chunk_46WU2TD4 = __esm({
|
|
8234
|
+
"../../packages/core/dist/chunk-46WU2TD4.js"() {
|
|
8235
8235
|
"use strict";
|
|
8236
8236
|
init_chunk_SNA2DXAC();
|
|
8237
8237
|
init_chunk_MIWZOSPI();
|
|
@@ -8742,6 +8742,7 @@ var init_chunk_T2RKGBYS = __esm({
|
|
|
8742
8742
|
organization_id: uuidSchema,
|
|
8743
8743
|
hub_name: external_exports.string().min(1, "hub_name is required"),
|
|
8744
8744
|
hub_description: external_exports.string().optional(),
|
|
8745
|
+
hub_type: external_exports.enum(["chat", "task"]).optional(),
|
|
8745
8746
|
kanban_statuses: kanbanStatusesArraySchema.nullable().optional(),
|
|
8746
8747
|
lanes: lanesArraySchema.nullable().optional(),
|
|
8747
8748
|
// Optional preview label for the first preview. Server-owned thereafter
|
|
@@ -8752,14 +8753,12 @@ var init_chunk_T2RKGBYS = __esm({
|
|
|
8752
8753
|
// Org tag ids. Ids that name none of the org's tags are dropped; a hub created with
|
|
8753
8754
|
// any is a tag change (`hubTagChangeRefusalSchema`).
|
|
8754
8755
|
tags: hubTagsField,
|
|
8755
|
-
// Localized end-user presentation (`hub-presentation.ts`)
|
|
8756
|
-
// `.passthrough()` and `createHub` writes all three, so an unvalidated map would persist.
|
|
8756
|
+
// Localized end-user presentation (`hub-presentation.ts`), as `updateHubBody` takes it.
|
|
8757
8757
|
display_names: hubDisplayNamesField,
|
|
8758
8758
|
welcome_messages: hubWelcomeMessagesField,
|
|
8759
8759
|
starter_questions: hubStarterQuestionsField,
|
|
8760
|
-
// Validated here for the same reason: `createHub` writes it.
|
|
8761
8760
|
end_user_daily_message_limit: endUserDailyMessageLimitField
|
|
8762
|
-
}).
|
|
8761
|
+
}).strict().superRefine(refineLaneReferences);
|
|
8763
8762
|
updateHubBody = external_exports.object({
|
|
8764
8763
|
support_model: external_exports.enum(["conversation", "account"]).optional(),
|
|
8765
8764
|
unassigned_policy: external_exports.enum(["default_team", "auto_assign_random", "auto_assign_round_robin", "pull_queue"]).optional(),
|
|
@@ -9724,7 +9723,7 @@ var init_chunk_T2RKGBYS = __esm({
|
|
|
9724
9723
|
}
|
|
9725
9724
|
});
|
|
9726
9725
|
|
|
9727
|
-
// ../../packages/core/dist/chunk-
|
|
9726
|
+
// ../../packages/core/dist/chunk-5RGL5RXH.js
|
|
9728
9727
|
function cacheReconcileIdCount(body) {
|
|
9729
9728
|
return body.conversations.length + body.histories.reduce((n, history) => n + history.conversation_ids.length, 0) + body.threads.length;
|
|
9730
9729
|
}
|
|
@@ -9770,27 +9769,33 @@ function parseCommunityLessonVideoUrl(url) {
|
|
|
9770
9769
|
const parts = /^https?:\/\/([^/?#]+)([^?#]*)(?:\?([^#]*))?(?:#.*)?$/i.exec(url.trim());
|
|
9771
9770
|
if (!parts) return null;
|
|
9772
9771
|
const host = parts[1].toLowerCase();
|
|
9773
|
-
const
|
|
9772
|
+
const path41 = parts[2] ?? "";
|
|
9774
9773
|
const query = parts[3] ?? "";
|
|
9775
9774
|
let id;
|
|
9776
9775
|
if (host === YOUTU_BE) {
|
|
9777
|
-
id = /^\/([A-Za-z0-9_-]{11})\/?$/.exec(
|
|
9776
|
+
id = /^\/([A-Za-z0-9_-]{11})\/?$/.exec(path41)?.[1];
|
|
9778
9777
|
} else if (YOUTUBE_HOSTS.has(host)) {
|
|
9779
|
-
if (
|
|
9778
|
+
if (path41 === "/watch" || path41 === "/watch/") {
|
|
9780
9779
|
id = query.split("&").find((param) => param.startsWith("v="))?.slice(2);
|
|
9781
9780
|
} else {
|
|
9782
|
-
id = YOUTUBE_ID_PATH.exec(
|
|
9781
|
+
id = YOUTUBE_ID_PATH.exec(path41)?.[1];
|
|
9783
9782
|
}
|
|
9784
9783
|
}
|
|
9785
9784
|
return id !== void 0 && YOUTUBE_ID.test(id) ? { provider: "youtube", id } : null;
|
|
9786
9785
|
}
|
|
9787
|
-
var connectionIdParam, connectorIdParam, listConnectionsQuery, deleteConnectionQuery, addConnectionBody, editConnectionBody, PRODUCTION_DIRECT_FIELDS, setProductionCredentialBody, registerWhatsappQuery, registerWhatsappBody, retryProvisioningQuery, resendDomainStatusQuery, initialValueCreateSchema, initialValueUpdateSchema, stateIdParam, listStatesQuery, stateNameSchema, createStateBody, updateStateBody, reorderStatesBody, variantAiModeSchema, experimentStatusSchema, overlayUpdateSchema, experimentIdParam, variantIdParam, listExperimentsQuery, listVariantsQuery, listOverridesQuery, deleteOverrideQuery, createExperimentBody, updateExperimentBody, setExperimentStatusBody, createVariantBody, updateVariantBody, upsertOverrideBody, orgTagIdParam, listOrgTagsQuery, ORG_TAG_NAME_REGEX, ORG_TAG_NAME_MAX_LENGTH, createOrgTagBody, updateOrgTagBody, orgCredentialIdParam, listOrgCredentialsQuery, createOrgCredentialBody, updateOrgCredentialBody, BASE_CREDENTIAL_MANAGED_BY, BASE_CREDENTIAL_NAME_MAX, baseCredentialNameSchema, createBaseCredentialLinkBody, baseCredentialLinkParams, baseCredentialLinkQuery, orgResourceIdParam, orgResourceFileIdParam, orgResourceFolderIdParam, hubOrgResourceParam, listOrgResourcesQuery, orgIdQuery, createOrgResourceBody, updateOrgResourceBody, createOrgResourceFolderBody, updateOrgResourceFolderBody, uploadOrgResourceFileBody, updateOrgResourceFileBody, uploadOrgSkillZipBody, linkOrgResourceBody, resourceIdParam, listResourcesQuery, createResourceBody, updateResourceBody, syncSkillsBody, resourceFileIdParam, listResourceFilesQuery, createResourceFileBody, updateResourceFileBody, uploadResourceFileBody, uploadSkillZipBody, resourceFolderIdParam, listResourceFoldersQuery, createResourceFolderBody, updateResourceFolderBody, agentResourceQuery, hubResourceQuery, linkAgentResourceBody, updateAgentResourceBody, unlinkAgentResourceQuery, navItemSchema, hubCountResetBody, navItemCountResetBody, typingBody, analyticsHubIdParam, analyticsVariableIdParam, updateVariablePinBody, updateAnalyticsViewBody, NUMERIC_FILTER_OPS, TEXT_FILTER_OPS, CATEGORICAL_FILTER_OPS, VARIABLE_FILTER_OPS, STRUCTURED_QUERY_OPS, STRUCTURED_QUERY_AGGREGATIONS, filterValueSchema, conversationsBody, analyticsConversationIdParam, messagesQuery, conversationDetailDataQuery, analyticsDataBody, structuredQueryBody, analyticsSqlBody, analyticsSqlTable, analyticsSqlSchemaQuery, getConversationsQuery, getMessagesQuery, appMessageSenderType, appMessageReceiverType, apiChannelMessage, apiChannelMessageBody, appMessageBody, appMessageResponse, CALL_STATUSES, callStatus, CALL_END_REASONS, callEndReason, CALL_TRANSPORTS, callTransport, CALL_PARTICIPANT_TYPES, callParticipantType, callInstant, MAX_SDP_OFFER_LENGTH, sdpDescription, sdpOffer, MAX_CALL_REQUEST_BODY_BYTES, hubScoped, callSummaryFields, callSummary, callIdParam, createCallBody, createCallResponse, callReadyBody, callHangupBody, callStatusQuery, MAX_REPORTED_DELEGATION_IDS, callDelegationId, callDelegationsBody, callResponse, MAX_CALL_RECORDING_GAPS, callRecordingGap, callRecordingNoteMetadata, evalCallInstant, EVAL_CALL_MIN_SECONDS, EVAL_CALL_MAX_SECONDS, EVAL_CALL_SPOKEN_LINE_MAX_CHARS, hubScoped2, createEvalCallBody, createEvalCallResponse, evalCallConversationQuery, evalCallRecordQuery, evalCallSpeaker, evalCallUtterance, evalCallTurn, evalCallRecording, evalCallRecordResponse, evalCallFinishResponse, updateUserBody, updateProfileBody, pathnameRegex, updatePreferencesBody, registerPushTokenBody, deletePushTokenQuery, recordAiDisclosureAcceptanceBody, activityQuery, deletionModeSchema, deletionRequestBody, CACHE_RECONCILE_MAX_IDS, CACHE_RECONCILE_MAX_HUBS, cacheReconcileScopeSchema, cachedConversationPageSchema, cachedHistoryThreadSchema, cachedPeopleThreadSchema, cacheReconcileBody, archiveHubIdParam, archiveConversationParams, archiveListQuery, archiveMessageObservabilityParams, conversationIdParam, conversationHubQuery, claimBody, releaseBody, transferBody, closeBody, additionalContextPayloadSchema, kanbanUpdateBody, annotateConversationBody, observabilityParams, observabilityListParams, deleteUserConversationsBody, deleteUserConversationsResponse, sendMessageBody, listConversationsQuery, flagSourceSchema, getConversationsResponse, getConversationDetailParams, getConversationDetailQuery, getConversationDetailResponse, listWhatsAppTemplatesQuery, sendWhatsAppTemplateBody, copilotSuggestBody, copilotSuggestResponse, CONSULT_THREAD_STATUSES, consultThreadStatus, CONSULT_THREAD_TITLE_MAX, consultThreadTitle, consultThreadSummary, listConsultThreadsQuery, listConsultThreadsResponse, createConsultThreadBody, createConsultThreadResponse, consultThreadIdParam, updateConsultThreadBody, updateConsultThreadResponse, CONSULT_AWAIT_TIMEOUT_MS, billingOrgIdParam, subscriptionItemParams, spendCapBody, growthPacksBody, portalSessionBody, checkoutSessionBody, updateBillingCurrencyBody, invoicesQuery, planKeySchema, billingStatusSchema, billingPlanTypeSchema, dataUsageCountersSchema, REKOR_ENTITLEMENT_CONTRACT_VERSION, entitlementProjectionSchema, rekorEntitlementRefreshMessageSchema, downloadBody, uploadQuery, SIGN_URL_MAX_EXPIRES_IN_SECONDS, signUrlBody, templateIdParam, listTemplatesQuery, deleteTemplateQuery, templateStatusQuery, createTemplateBody, updateTemplateBody, submitTemplateBody, testTemplateBody, CONTACT_ENVIRONMENTS, CONTACT_NAME_MAX, CONTACT_EMAIL_MAX, CONTACT_PHONE_INPUT_MAX, CONTACT_INSTAGRAM_SID_MAX, CONTACT_METADATA_MAX_BYTES, CONTACT_LABEL_MAX, CONTACT_LABELS_MAX, CONTACT_SCOPE_TAGS_MAX, CONTACT_PAGE_MAX, CONTACT_IMPORT_BATCH_MAX, CONTACT_SEARCH_MAX, CONTACT_LIST_NAME_MAX, CONTACT_LIST_NAME_REGEX, CONTACT_LIST_DESCRIPTION_MAX, CONTACT_LIST_FILTER_MAX, CONTACT_LIST_PINS_PER_REQUEST_MAX, SUPPRESSION_SCOPES, SUPPRESSION_ID_REGEX, contactName, contactPhone, contactEmail, contactInstagramSid, contactLabel, contactLabels, contactMetadata, scopeTagIds, contactVisibilitySchema, hasIdentity, IDENTITY_REQUIRED, contactBookOrgQuery, contactBookContactParam, contactBookListParam, contactBookHubParam, contactBookPageQuery, listContactBookContactsQuery, listHubContactBookContactsQuery, contactBookLinkCandidatesQuery, listContactBookListMembersQuery, contactRowFields, createContactBookContactBody, updateContactBookContactBody, contactImportRowSchema, importContactBookContactsBody, linkContactBookContactBody, listName, contactListFilterSchema, createContactBookListBody, updateContactBookListBody, updateContactBookListPinsBody, suppressionIdentitySchema, createContactBookSuppressionBody, contactBookSuppressionParam, listContactBookSuppressionsQuery, CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH, contactBookEmailUnsubscribeTokenSchema, contactBookEmailUnsubscribeQuery, orgListReferenceSchema, hubListNameSchema, HUB_LIST_FILTER_REQUIRED, hubContactListFilterSchema, hubContactListIdParam, hubContactListHubQuery, description, createHubContactListBody, updateHubContactListBody, AUTOMATION_TRIGGER_TYPES, AUTOMATION_EVENT_TYPES, AUTOMATION_TARGET_TYPES, AUTOMATION_ACTION_TYPES, AUTOMATION_CHANNEL_TYPES, AUTOMATION_PURPOSES, AUTOMATION_NAME_MAX, AUTOMATION_DESCRIPTION_MAX, AUTOMATION_INSTRUCTIONS_MAX, AUTOMATION_TEXT_MAX, AUTOMATION_CRON_MAX, AUTOMATION_WEBHOOK_AUTHS, AUTOMATION_WEBHOOK_HEADERS, AUTOMATION_WEBHOOK_BODY_MAX_BYTES, AUTOMATION_WEBHOOK_HEADER_NAME_MAX, AUTOMATION_WEBHOOK_MOUNT, WEBHOOK_RESERVED_HEADERS, automationTriggerSchema, automationTargetSchema, automationActionSchema, automationGateSchema, automationName, automationDescription, automationIdParam, automationHubQuery, automationWebhookParams, AUTOMATION_RUNS_PAGE_MAX, listAutomationRunsQuery, createAutomationBody, updateAutomationBody, AUTOMATION_ALERT_CATEGORIES, AUTOMATION_BLOCKED_ALERT_CATEGORY, automationEventSubscriberParam, VERSION_PART, MOBILE_APP_VERSION_RE, mobileAppVersionSchema, MOBILE_PLATFORMS, mobilePlatformSchema, mobileVersionPolicyQuery, mobileVersionPolicySchema, mobileVersionPolicyResponse, adminOrgIdParam, adminOrgAdminIdParam, adminUserIdParam, PRICING_PLAN_ID_RE, adminPlanIdParam, adminOrganizationsQuery, adminUserSearchQuery, kanbanSlugMigrationQuery, adjustQuotaBody, updatePlanBody, addAdminUserBody, MAX_VOICE_CALL_MINUTE_OPS, voiceCallMinuteOps, AUTOMATION_GATE_EVALUATIONS_PER_OPERATION_BOUND, automationGateEvaluationsPerOperation, AUTOMATION_RUN_RETENTION_DAYS_MAX, automationRunRetentionDays, updatePlatformConfigBody, updateFreeOrgLimitBody, adminScopedTargetShape, adminScopedTargetBody, BRANCHING_ORPHAN_ENTITY_TYPES, BRANCHING_ORPHAN_MAX_TARGETS, adminRepairBranchingOrphansBody, adminRepairLegacyOrgGrantsBody, adminRepairCompedPlansBody, updatePricingPlanBody, dataExplorerSearchQuery, dataExplorerOrgIdParam, dataExplorerHubIdParam, dataExplorerConvIdParam, dataExplorerDoInstancesQuery, dataExplorerNsIdParam, dataExplorerKvKeysBody, dataExplorerKvKeyBody, uuidOrHexId, dataExplorerDeleteOrgParam, dataExplorerDeleteHubParam, dataExplorerDeleteConvParam, dataExplorerDeleteUserParam, dataExplorerDeleteQuery, dataExplorerKvDeleteBody, pitrDoNamespace, pitrBookmarkParam, pitrRestoreBody, healthSamplingTriggerBody, dataExplorerDebugDoType, DEBUG_READ_MAX_LIMIT, DEBUG_READ_DEFAULT_LIMIT, DEBUG_DO_HEX_PREFIX, debugDoEntityId, debugTableName, dataExplorerDebugTablesParam, dataExplorerDebugRowsParam, dataExplorerDebugRowsQuery, dataExplorerDebugAnalyticsTable, debugUuid, dataExplorerDebugAnalyticsRowsParam, dataExplorerDebugAnalyticsRowsQuery, debugHubConvParam, debugMessageIdQuery, sandboxEgressPolicy, SANDBOX_EXEC_MAX_CMD_LENGTH, SANDBOX_EXEC_MAX_ALLOWLIST, SANDBOX_EXEC_MAX_TIMEOUT_MS, adminSandboxExecBody, META_ENTITY_ID_PATTERN, metaEntityId, whatsappCallbackBody, authMeResponse, wsTicketResponse, sessionCheckResponse, logoutResponse, magicCodeSendBody, magicCodeSendResponse, magicCodeVerifyBody, magicCodeVerifyResponse, passwordLoginBody, stateOperationBody, conversationListResourcesQuery, conversationReadResourceQuery, listPendingSchedulesQuery, listHubAggregateSchedulesQuery, reportSourceSchema, intakeReportSourceSchema, reportClassificationSchema, reportStatusSchema, reportLocaleSchema, reportMessageAuthorRoleSchema, reportMessageSchema, sentryRefStatusSchema, createReportBody, editReportBody, reporterReportSummarySchema, reporterListResponseSchema, getReportResponseSchema, reporterTransitionResponseSchema, contestReportBody, reporterListQuery, listReportsQuery, GITHUB_ISSUE_URL_REGEX, githubIssueUrlSchema, transitionReportBody, groupReportsBody, holdReportBody, clickhouseReadyResponse, bootstrapQuery, MAX_NAME_LENGTH, MAX_VALUE_LENGTH, MAX_TAGS, MAX_TAG_LENGTH, MAX_SCOPE_HUBS, MAX_SCOPE_TAGS, vaultSecretScopeSchema, vaultCreateBody, vaultUpdateBody, MAX_NAME_LENGTH2, MAX_HUBS_PER_GRANT, MAX_HUB_TAGS_PER_GRANT, MAX_GRANTS, permissionEnum, grantScopeSchema, grantSchema, createTokenBody, tokenOrgIdParam, invitePreviewQuery, inviteRolePreviewResponse, inviteContactPreviewResponse, invitePreviewResponse, inviteUnsubscribeBody, jsonSchemaSchema, hubUserIdentity, stateValueEntry, getHubUserContextResponse, updateHubUserBody, updateHubUserResponse, unlockHubUserNameResponse, stateResetParams, stateResetResponse, hubAlertsParam, hubAlertSeverity, hubAlert, hubAlertsListResponse, noticeSeveritySchema, noticeStatusSchema, NOTICE_SCOPE_REGEX, noticeScopeSchema, noticeLinkSchema, createNoticeBody, updateNoticeBody, listNoticesQuery, noticeIdParamSchema, ADMIN_SKILL_NAME_REGEX, adminSkillNameParam, REKOR_ACTOR_TYPE_HEADER, REKOR_ACTOR_ID_HEADER, REKOR_ACTOR_LABEL_HEADER, REKOR_ACTOR_ORG_HEADER, REKOR_ACTOR_ADMIN_HEADER, REKOR_CLIENT_HEADER, REKOR_TOOL_ID_HEADER, REKOR_CLIENTS, rekorClientSchema, MAX_REKOR_TOOL_ID_LENGTH, rekorToolIdSchema, DATA_PROXY_MOUNT, DATA_PROXY_PREFIX, REKOR_V1_PREFIX, DATA_PROXY_ORG_QUERY_PARAM, MAX_PROVISIONING_BODY_BYTES, dataProxyQuery, rekorAttestation, REQUIRED_REKOR_ATTESTATION_HEADERS, BASE_DESTRUCTION_MOUNT, BASE_DESTRUCTION_PREFIX, BASE_DESTRUCTION_CONFIRM_PATH, baseDestructionConfirmBody, BASE_DESTRUCTION_GRACE_MS, BASE_DESTRUCTION_CONFIRM_TTL_MS, BASE_DESTRUCTION_ORG_QUERY_PARAM, baseDestructionQuery, baseDestructionParam, baseDestructionInitiateBody, baseDestructionStatus, baseDestructionRequest, baseDestructionListResponse, baseDestructionInitiateResponse, baseDestructionConfirmResponse, baseDestructionCancelResponse, rekorBaseChangeMessageSchema, PEOPLE_CONTACT_PAGE_MAX, PEOPLE_CONTACT_PAGE_DEFAULT, PEOPLE_CONTACT_EMAIL_MAX_LENGTH, peopleUserIdSchema, peopleInviteEmailSchema, peopleContactInviteBody, peopleContactInviteIdParam, peopleContactUserIdParam, peopleContactBlockBody, peopleContactPageQuery, peopleOutgoingInviteSchema, peopleContactPersonSchema, peopleContactSchema, peopleIncomingInviteSchema, peopleBlockedUserSchema, PEOPLE_THREAD_MESSAGE_MAX_LENGTH, PEOPLE_THREAD_MESSAGES_PAGE_MAX, PEOPLE_THREAD_PAGE_MAX, PEOPLE_THREAD_PAGE_DEFAULT, PEOPLE_GROUP_NAME_MAX_LENGTH, PEOPLE_GROUP_AVATAR_URL_MAX_LENGTH, PEOPLE_THREAD_ATTACHMENT_MAX_BYTES, PEOPLE_THREAD_ATTACHMENT_NAME_MAX_LENGTH, PEOPLE_ATTACHMENT_STORAGE_PER_USER_BYTES, PEOPLE_THREAD_ATTACHMENT_MAX_MB, PEOPLE_THREAD_ATTACHMENT_URL_TTL_SECONDS, PEOPLE_THREAD_ATTACHMENT_URLS_MAX, peopleThreadIdParam, peopleThreadMessageIdParam, peopleThreadListQuery, peopleThreadMessagesQuery, peopleThreadAttachmentUploadSchema, peopleThreadSendBody, peopleThreadAttachmentUrlsBody, peopleThreadReadBody, peopleThreadSettingsBody, peopleGroupNameSchema, peopleGroupAvatarUrlSchema, peopleGroupCreateBody, peopleOrgDirectBody, peopleGroupUpdateBody, peopleGroupInviteBody, peopleOrgErasureBody, peopleGroupLeaveBody, peopleGroupInviteIdParam, peopleGroupIncomingInviteIdParam, peopleGroupMemberParam, peopleThreadKindSchema, peopleThreadMemberSchema, peopleThreadLastMessageSchema, peopleGroupRoleSchema, peopleGroupSummarySchema, peopleThreadSummarySchema, peopleThreadAttachmentSchema, peopleThreadMessageSchema, peopleGroupMemberSchema, peopleGroupSentInviteSchema, peopleGroupIncomingInviteSchema, PEOPLE_REPORT_DETAILS_MAX_LENGTH, peopleReportReasonSchema, reportDetailsSchema, reportFields, peopleReportBody, PEOPLE_MODERATION_NOTE_MAX_LENGTH, ADMIN_PEOPLE_REPORTS_PAGE_MAX, ADMIN_PEOPLE_REPORTS_PAGE_DEFAULT, peopleModerationActionSchema, moderationNoteSchema, adminPeopleReportListQuery, adminPeopleReportIdParam, adminPeopleMessageRemovalBody, adminPeopleSuspendBody, adminPeopleNoteBody, COMMUNITIES_PER_ORG_MAX, COMMUNITY_NAME_MAX_LENGTH, COMMUNITY_REGISTRY_CLOSED_REASON, COMMUNITY_JOIN_SOURCE_MAX_LENGTH, COMMUNITY_JOIN_FLOW_TOKEN_MAX_LENGTH, COMMUNITY_ROOMS_MAX, COMMUNITY_ROOM_NAME_MAX_LENGTH, COMMUNITY_MESSAGE_MAX_LENGTH, COMMUNITY_MESSAGES_PAGE_SIZE, COMMUNITY_ATTACHMENT_URLS_MAX, COMMUNITY_POST_TITLE_MAX_LENGTH, COMMUNITY_POST_SEARCH_MAX_LENGTH, COMMUNITY_MEMBER_STATUSES, COMMUNITY_RULES_MAX_LENGTH, COMMUNITY_SLOW_MODE_MAX_SECONDS, COMMUNITY_COURSES_LABEL_MAX_LENGTH, COMMUNITY_LIVE_TICKET_PREFIX, COMMUNITY_STATS_DAYS, communityNameSchema, adminCreateCommunityBody, communityIdParam, communityJoinTokenSchema, communityJoinSourceSchema, communityJoinPreviewQuery, communityJoinBody, communityRoomKindSchema, communityRoomNameSchema, communityRoomIdParam, communityMessageIdParam, communityRoomCreateBody, communityRoomUpdateBody, communityMessagesQuery, communityMessageBodySchema, communityPostTitleSchema, communityAttachmentUploadSchema, communitySendBody, communityEditBody, communityPostIdParam, communityPostsQuery, communityAnswerBody, communityReadBody, communityAttachmentUrlsBody, communityPersonParam, communityOrgParam, communityPeopleQuery, communityCoursesLabelSchema, communitySettingsBody, communityBanBody, communityLockBody, communityPinBody, communityModLogQuery, communityReportsQuery, communityReportIdParam, communityLiveTicketSchema, communityLiveQuery, communityMuteBody, communityEmailUnsubscribeTokenSchema, COMMUNITY_EMAIL_UNSUBSCRIBE_PATH, communityEmailUnsubscribeQuery, COMMUNITY_COURSE_TITLE_MAX_LENGTH, COMMUNITY_COURSE_DESCRIPTION_MAX_LENGTH, COMMUNITY_LESSON_BODY_MAX_LENGTH, COMMUNITY_LESSON_LINKS_MAX, COMMUNITY_LESSON_URL_MAX_LENGTH, YOUTUBE_ID, YOUTUBE_HOSTS, YOUTU_BE, YOUTUBE_ID_PATH, communityCourseIdParam, communityCourseModuleIdParam, communityLessonIdParam, communityCourseTitleSchema, communityCourseDescriptionSchema, communityCoursePositionSchema, communityLessonVideoUrlSchema, communityLessonLinkSchema, communityLessonLinksSchema, communityLessonBodySchema, communityCourseCreateBody, communityCourseUpdateBody, communityModuleCreateBody, communityModuleUpdateBody, communityLessonCreateBody, communityLessonUpdateBody, COMMUNITY_LESSON_TREE_MAX_BYTES, COMMUNITY_LESSON_LINK_PROTOCOLS, LINK_SCHEME, adminSetGuideHubBody, GUIDE_HUB_INELIGIBLE_REASON;
|
|
9788
|
-
|
|
9789
|
-
|
|
9786
|
+
function communityLessonVideoWatchUrl(video) {
|
|
9787
|
+
switch (video.provider) {
|
|
9788
|
+
case "youtube":
|
|
9789
|
+
return `https://www.youtube.com/watch?v=${video.id}`;
|
|
9790
|
+
}
|
|
9791
|
+
}
|
|
9792
|
+
var connectionIdParam, connectorIdParam, listConnectionsQuery, deleteConnectionQuery, addConnectionBody, editConnectionBody, PRODUCTION_DIRECT_FIELDS, setProductionCredentialBody, registerWhatsappQuery, registerWhatsappBody, retryProvisioningQuery, resendDomainStatusQuery, initialValueCreateSchema, initialValueUpdateSchema, stateIdParam, listStatesQuery, stateNameSchema, createStateBody, updateStateBody, reorderStatesBody, variantAiModeSchema, experimentStatusSchema, overlayUpdateSchema, experimentIdParam, variantIdParam, listExperimentsQuery, listVariantsQuery, listOverridesQuery, deleteOverrideQuery, createExperimentBody, updateExperimentBody, setExperimentStatusBody, createVariantBody, updateVariantBody, upsertOverrideBody, orgTagIdParam, listOrgTagsQuery, ORG_TAG_NAME_REGEX, ORG_TAG_NAME_MAX_LENGTH, createOrgTagBody, updateOrgTagBody, orgCredentialIdParam, listOrgCredentialsQuery, createOrgCredentialBody, updateOrgCredentialBody, BASE_CREDENTIAL_MANAGED_BY, BASE_CREDENTIAL_NAME_MAX, baseCredentialNameSchema, createBaseCredentialLinkBody, baseCredentialLinkParams, baseCredentialLinkQuery, orgResourceIdParam, orgResourceFileIdParam, orgResourceFolderIdParam, hubOrgResourceParam, listOrgResourcesQuery, orgIdQuery, createOrgResourceBody, updateOrgResourceBody, createOrgResourceFolderBody, updateOrgResourceFolderBody, uploadOrgResourceFileBody, updateOrgResourceFileBody, uploadOrgSkillZipBody, linkOrgResourceBody, resourceIdParam, listResourcesQuery, createResourceBody, updateResourceBody, syncSkillsBody, resourceFileIdParam, listResourceFilesQuery, createResourceFileBody, updateResourceFileBody, uploadResourceFileBody, uploadSkillZipBody, resourceFolderIdParam, listResourceFoldersQuery, createResourceFolderBody, updateResourceFolderBody, agentResourceQuery, hubResourceQuery, linkAgentResourceBody, updateAgentResourceBody, unlinkAgentResourceQuery, navItemSchema, hubCountResetBody, navItemCountResetBody, typingBody, analyticsHubIdParam, analyticsVariableIdParam, updateVariablePinBody, updateAnalyticsViewBody, NUMERIC_FILTER_OPS, TEXT_FILTER_OPS, CATEGORICAL_FILTER_OPS, VARIABLE_FILTER_OPS, STRUCTURED_QUERY_OPS, STRUCTURED_QUERY_AGGREGATIONS, filterValueSchema, conversationsBody, analyticsConversationIdParam, messagesQuery, conversationDetailDataQuery, analyticsDataBody, structuredQueryBody, analyticsSqlBody, analyticsSqlTable, analyticsSqlSchemaQuery, getConversationsQuery, getMessagesQuery, appMessageSenderType, appMessageReceiverType, apiChannelMessage, apiChannelMessageBody, appMessageBody, appMessageResponse, CALL_STATUSES, callStatus, CALL_END_REASONS, callEndReason, CALL_TRANSPORTS, callTransport, CALL_PARTICIPANT_TYPES, callParticipantType, callInstant, MAX_SDP_OFFER_LENGTH, sdpDescription, sdpOffer, MAX_CALL_REQUEST_BODY_BYTES, hubScoped, callSummaryFields, callSummary, callIdParam, createCallBody, createCallResponse, callReadyBody, callHangupBody, callStatusQuery, MAX_REPORTED_DELEGATION_IDS, callDelegationId, callDelegationsBody, callResponse, MAX_CALL_RECORDING_GAPS, callRecordingGap, callRecordingNoteMetadata, evalCallInstant, EVAL_CALL_MIN_SECONDS, EVAL_CALL_MAX_SECONDS, EVAL_CALL_SPOKEN_LINE_MAX_CHARS, hubScoped2, createEvalCallBody, createEvalCallResponse, evalCallConversationQuery, evalCallRecordQuery, evalCallSpeaker, evalCallUtterance, evalCallTurn, evalCallRecording, evalCallRecordResponse, evalCallFinishResponse, updateUserBody, updateProfileBody, pathnameRegex, updatePreferencesBody, registerPushTokenBody, deletePushTokenQuery, recordAiDisclosureAcceptanceBody, activityQuery, deletionModeSchema, deletionRequestBody, CACHE_RECONCILE_MAX_IDS, CACHE_RECONCILE_MAX_HUBS, cacheReconcileScopeSchema, cachedConversationPageSchema, cachedHistoryThreadSchema, cachedPeopleThreadSchema, cacheReconcileBody, archiveHubIdParam, archiveConversationParams, archiveListQuery, archiveMessageObservabilityParams, conversationIdParam, conversationHubQuery, claimBody, releaseBody, transferBody, closeBody, additionalContextPayloadSchema, kanbanUpdateBody, annotateConversationBody, observabilityParams, observabilityListParams, deleteUserConversationsBody, deleteUserConversationsResponse, sendMessageBody, listConversationsQuery, flagSourceSchema, getConversationsResponse, getConversationDetailParams, getConversationDetailQuery, getConversationDetailResponse, listWhatsAppTemplatesQuery, sendWhatsAppTemplateBody, copilotSuggestBody, copilotSuggestResponse, CONSULT_THREAD_STATUSES, consultThreadStatus, CONSULT_THREAD_TITLE_MAX, consultThreadTitle, consultThreadSummary, listConsultThreadsQuery, listConsultThreadsResponse, createConsultThreadBody, createConsultThreadResponse, consultThreadIdParam, updateConsultThreadBody, updateConsultThreadResponse, CONSULT_AWAIT_TIMEOUT_MS, billingOrgIdParam, subscriptionItemParams, spendCapBody, growthPacksBody, portalSessionBody, checkoutSessionBody, updateBillingCurrencyBody, invoicesQuery, planKeySchema, billingStatusSchema, billingPlanTypeSchema, dataUsageCountersSchema, REKOR_ENTITLEMENT_CONTRACT_VERSION, entitlementProjectionSchema, rekorEntitlementRefreshMessageSchema, downloadBody, uploadQuery, SIGN_URL_MAX_EXPIRES_IN_SECONDS, signUrlBody, templateIdParam, listTemplatesQuery, deleteTemplateQuery, templateStatusQuery, createTemplateBody, updateTemplateBody, submitTemplateBody, testTemplateBody, CONTACT_ENVIRONMENTS, CONTACT_NAME_MAX, CONTACT_EMAIL_MAX, CONTACT_PHONE_INPUT_MAX, CONTACT_INSTAGRAM_SID_MAX, CONTACT_METADATA_MAX_BYTES, CONTACT_LABEL_MAX, CONTACT_LABELS_MAX, CONTACT_SCOPE_TAGS_MAX, CONTACT_PAGE_MAX, CONTACT_IMPORT_BATCH_MAX, CONTACT_SEARCH_MAX, CONTACT_LIST_NAME_MAX, CONTACT_LIST_NAME_REGEX, CONTACT_LIST_DESCRIPTION_MAX, CONTACT_LIST_FILTER_MAX, CONTACT_LIST_PINS_PER_REQUEST_MAX, SUPPRESSION_SCOPES, SUPPRESSION_ID_REGEX, contactName, contactPhone, contactEmail, contactInstagramSid, contactLabel, contactLabels, contactMetadata, scopeTagIds, contactVisibilitySchema, hasIdentity, IDENTITY_REQUIRED, contactBookOrgQuery, contactBookContactParam, contactBookListParam, contactBookHubParam, contactBookPageQuery, listContactBookContactsQuery, listHubContactBookContactsQuery, contactBookLinkCandidatesQuery, listContactBookListMembersQuery, contactRowFields, createContactBookContactBody, updateContactBookContactBody, contactImportRowSchema, importContactBookContactsBody, linkContactBookContactBody, listName, contactListFilterSchema, createContactBookListBody, updateContactBookListBody, updateContactBookListPinsBody, suppressionIdentitySchema, createContactBookSuppressionBody, contactBookSuppressionParam, listContactBookSuppressionsQuery, CONTACT_BOOK_EMAIL_UNSUBSCRIBE_PATH, contactBookEmailUnsubscribeTokenSchema, contactBookEmailUnsubscribeQuery, orgListReferenceSchema, hubListNameSchema, HUB_LIST_FILTER_REQUIRED, hubContactListFilterSchema, hubContactListIdParam, hubContactListHubQuery, description, createHubContactListBody, updateHubContactListBody, AUTOMATION_TRIGGER_TYPES, AUTOMATION_EVENT_TYPES, AUTOMATION_TARGET_TYPES, AUTOMATION_ACTION_TYPES, AUTOMATION_CHANNEL_TYPES, AUTOMATION_PURPOSES, AUTOMATION_NAME_MAX, AUTOMATION_DESCRIPTION_MAX, AUTOMATION_INSTRUCTIONS_MAX, AUTOMATION_TEXT_MAX, AUTOMATION_CRON_MAX, AUTOMATION_WEBHOOK_AUTHS, AUTOMATION_WEBHOOK_HEADERS, AUTOMATION_WEBHOOK_BODY_MAX_BYTES, AUTOMATION_WEBHOOK_HEADER_NAME_MAX, AUTOMATION_WEBHOOK_MOUNT, WEBHOOK_RESERVED_HEADERS, automationTriggerSchema, automationTargetSchema, automationActionSchema, automationGateSchema, automationName, automationDescription, automationIdParam, automationHubQuery, automationWebhookParams, AUTOMATION_RUNS_PAGE_MAX, listAutomationRunsQuery, createAutomationBody, updateAutomationBody, AUTOMATION_ALERT_CATEGORIES, AUTOMATION_BLOCKED_ALERT_CATEGORY, automationEventSubscriberParam, VERSION_PART, MOBILE_APP_VERSION_RE, mobileAppVersionSchema, MOBILE_PLATFORMS, mobilePlatformSchema, mobileVersionPolicyQuery, mobileVersionPolicySchema, mobileVersionPolicyResponse, adminOrgIdParam, adminOrgAdminIdParam, adminUserIdParam, PRICING_PLAN_ID_RE, adminPlanIdParam, adminOrganizationsQuery, adminUserSearchQuery, kanbanSlugMigrationQuery, adjustQuotaBody, updatePlanBody, addAdminUserBody, MAX_VOICE_CALL_MINUTE_OPS, voiceCallMinuteOps, AUTOMATION_GATE_EVALUATIONS_PER_OPERATION_BOUND, automationGateEvaluationsPerOperation, AUTOMATION_RUN_RETENTION_DAYS_MAX, automationRunRetentionDays, updatePlatformConfigBody, updateFreeOrgLimitBody, adminScopedTargetShape, adminScopedTargetBody, BRANCHING_ORPHAN_ENTITY_TYPES, BRANCHING_ORPHAN_MAX_TARGETS, adminRepairBranchingOrphansBody, adminRepairLegacyOrgGrantsBody, adminRepairCompedPlansBody, updatePricingPlanBody, dataExplorerSearchQuery, dataExplorerOrgIdParam, dataExplorerHubIdParam, dataExplorerConvIdParam, dataExplorerDoInstancesQuery, dataExplorerNsIdParam, dataExplorerKvKeysBody, dataExplorerKvKeyBody, uuidOrHexId, dataExplorerDeleteOrgParam, dataExplorerDeleteHubParam, dataExplorerDeleteConvParam, dataExplorerDeleteUserParam, dataExplorerDeleteQuery, dataExplorerKvDeleteBody, pitrDoNamespace, pitrBookmarkParam, pitrRestoreBody, healthSamplingTriggerBody, dataExplorerDebugDoType, DEBUG_READ_MAX_LIMIT, DEBUG_READ_DEFAULT_LIMIT, DEBUG_DO_HEX_PREFIX, debugDoEntityId, debugTableName, dataExplorerDebugTablesParam, dataExplorerDebugRowsParam, dataExplorerDebugRowsQuery, dataExplorerDebugAnalyticsTable, debugUuid, dataExplorerDebugAnalyticsRowsParam, dataExplorerDebugAnalyticsRowsQuery, debugHubConvParam, debugMessageIdQuery, sandboxEgressPolicy, SANDBOX_EXEC_MAX_CMD_LENGTH, SANDBOX_EXEC_MAX_ALLOWLIST, SANDBOX_EXEC_MAX_TIMEOUT_MS, adminSandboxExecBody, META_ENTITY_ID_PATTERN, metaEntityId, whatsappCallbackBody, authMeResponse, wsTicketResponse, sessionCheckResponse, logoutResponse, magicCodeSendBody, magicCodeSendResponse, magicCodeVerifyBody, magicCodeVerifyResponse, passwordLoginBody, stateOperationBody, conversationListResourcesQuery, conversationReadResourceQuery, listPendingSchedulesQuery, listHubAggregateSchedulesQuery, reportSourceSchema, intakeReportSourceSchema, reportClassificationSchema, reportStatusSchema, reportLocaleSchema, reportMessageAuthorRoleSchema, reportMessageSchema, sentryRefStatusSchema, createReportBody, editReportBody, reporterReportSummarySchema, reporterListResponseSchema, getReportResponseSchema, reporterTransitionResponseSchema, contestReportBody, reporterListQuery, listReportsQuery, GITHUB_ISSUE_URL_REGEX, githubIssueUrlSchema, transitionReportBody, groupReportsBody, holdReportBody, clickhouseReadyResponse, bootstrapQuery, MAX_NAME_LENGTH, MAX_VALUE_LENGTH, MAX_TAGS, MAX_TAG_LENGTH, MAX_SCOPE_HUBS, MAX_SCOPE_TAGS, vaultSecretScopeSchema, vaultCreateBody, vaultUpdateBody, MAX_NAME_LENGTH2, MAX_HUBS_PER_GRANT, MAX_HUB_TAGS_PER_GRANT, MAX_GRANTS, permissionEnum, grantScopeSchema, grantSchema, createTokenBody, tokenOrgIdParam, invitePreviewQuery, inviteRolePreviewResponse, inviteContactPreviewResponse, invitePreviewResponse, inviteUnsubscribeBody, jsonSchemaSchema, hubUserIdentity, stateValueEntry, getHubUserContextResponse, updateHubUserBody, updateHubUserResponse, unlockHubUserNameResponse, stateResetParams, stateResetResponse, hubAlertsParam, hubAlertSeverity, hubAlert, hubAlertsListResponse, noticeSeveritySchema, noticeStatusSchema, NOTICE_SCOPE_REGEX, noticeScopeSchema, noticeLinkSchema, createNoticeBody, updateNoticeBody, listNoticesQuery, noticeIdParamSchema, ADMIN_SKILL_NAME_REGEX, adminSkillNameParam, REKOR_ACTOR_TYPE_HEADER, REKOR_ACTOR_ID_HEADER, REKOR_ACTOR_LABEL_HEADER, REKOR_ACTOR_ORG_HEADER, REKOR_ACTOR_ADMIN_HEADER, REKOR_CLIENT_HEADER, REKOR_TOOL_ID_HEADER, REKOR_CLIENTS, rekorClientSchema, MAX_REKOR_TOOL_ID_LENGTH, rekorToolIdSchema, DATA_PROXY_MOUNT, DATA_PROXY_PREFIX, REKOR_V1_PREFIX, DATA_PROXY_ORG_QUERY_PARAM, MAX_PROVISIONING_BODY_BYTES, dataProxyQuery, rekorAttestation, REQUIRED_REKOR_ATTESTATION_HEADERS, BASE_DESTRUCTION_MOUNT, BASE_DESTRUCTION_PREFIX, BASE_DESTRUCTION_CONFIRM_PATH, baseDestructionConfirmBody, BASE_DESTRUCTION_GRACE_MS, BASE_DESTRUCTION_CONFIRM_TTL_MS, BASE_DESTRUCTION_ORG_QUERY_PARAM, baseDestructionQuery, baseDestructionParam, baseDestructionInitiateBody, baseDestructionStatus, baseDestructionRequest, baseDestructionListResponse, baseDestructionInitiateResponse, baseDestructionConfirmResponse, baseDestructionCancelResponse, rekorBaseChangeMessageSchema, PEOPLE_CONTACT_PAGE_MAX, PEOPLE_CONTACT_PAGE_DEFAULT, PEOPLE_CONTACT_EMAIL_MAX_LENGTH, peopleUserIdSchema, peopleInviteEmailSchema, peopleContactInviteBody, peopleContactInviteIdParam, peopleContactUserIdParam, peopleContactBlockBody, peopleContactPageQuery, peopleOutgoingInviteSchema, peopleContactPersonSchema, peopleContactSchema, peopleIncomingInviteSchema, peopleBlockedUserSchema, PEOPLE_THREAD_MESSAGE_MAX_LENGTH, PEOPLE_THREAD_MESSAGES_PAGE_MAX, PEOPLE_THREAD_PAGE_MAX, PEOPLE_THREAD_PAGE_DEFAULT, PEOPLE_GROUP_NAME_MAX_LENGTH, PEOPLE_GROUP_AVATAR_URL_MAX_LENGTH, PEOPLE_THREAD_ATTACHMENT_MAX_BYTES, PEOPLE_THREAD_ATTACHMENT_NAME_MAX_LENGTH, PEOPLE_ATTACHMENT_STORAGE_PER_USER_BYTES, PEOPLE_THREAD_ATTACHMENT_MAX_MB, PEOPLE_THREAD_ATTACHMENT_URL_TTL_SECONDS, PEOPLE_THREAD_ATTACHMENT_URLS_MAX, peopleThreadIdParam, peopleThreadMessageIdParam, peopleThreadListQuery, peopleThreadMessagesQuery, peopleThreadAttachmentUploadSchema, peopleThreadSendBody, peopleThreadAttachmentUrlsBody, peopleThreadReadBody, peopleThreadSettingsBody, peopleGroupNameSchema, peopleGroupAvatarUrlSchema, peopleGroupCreateBody, peopleOrgDirectBody, peopleGroupUpdateBody, peopleGroupInviteBody, peopleOrgErasureBody, peopleGroupLeaveBody, peopleGroupInviteIdParam, peopleGroupIncomingInviteIdParam, peopleGroupMemberParam, peopleThreadKindSchema, peopleThreadMemberSchema, peopleThreadLastMessageSchema, peopleGroupRoleSchema, peopleGroupSummarySchema, peopleThreadSummarySchema, peopleThreadAttachmentSchema, peopleThreadMessageSchema, peopleGroupMemberSchema, peopleGroupSentInviteSchema, peopleGroupIncomingInviteSchema, PEOPLE_REPORT_DETAILS_MAX_LENGTH, peopleReportReasonSchema, reportDetailsSchema, reportFields, peopleReportBody, PEOPLE_MODERATION_NOTE_MAX_LENGTH, ADMIN_PEOPLE_REPORTS_PAGE_MAX, ADMIN_PEOPLE_REPORTS_PAGE_DEFAULT, peopleModerationActionSchema, moderationNoteSchema, adminPeopleReportListQuery, adminPeopleReportIdParam, adminPeopleMessageRemovalBody, adminPeopleSuspendBody, adminPeopleNoteBody, COMMUNITIES_PER_ORG_MAX, COMMUNITY_NAME_MAX_LENGTH, COMMUNITY_REGISTRY_CLOSED_REASON, COMMUNITY_ADMISSIONS, COMMUNITY_ACCESS_ADD_MAX, COMMUNITY_JOIN_SOURCE_MAX_LENGTH, COMMUNITY_JOIN_FLOW_TOKEN_MAX_LENGTH, COMMUNITY_ROOMS_MAX, COMMUNITY_ROOM_NAME_MAX_LENGTH, COMMUNITY_MESSAGE_MAX_LENGTH, COMMUNITY_MESSAGES_PAGE_SIZE, COMMUNITY_ATTACHMENT_URLS_MAX, COMMUNITY_POST_TITLE_MAX_LENGTH, COMMUNITY_POST_SEARCH_MAX_LENGTH, COMMUNITY_MEMBER_STATUSES, COMMUNITY_RULES_MAX_LENGTH, COMMUNITY_SLOW_MODE_MAX_SECONDS, COMMUNITY_COURSES_LABEL_MAX_LENGTH, COMMUNITY_LIVE_TICKET_PREFIX, COMMUNITY_STATS_DAYS, communityNameSchema, adminCreateCommunityBody, communityIdParam, communityJoinTokenSchema, communityJoinSourceSchema, communityJoinPreviewQuery, communityJoinBody, communityRoomKindSchema, communityRoomNameSchema, communityRoomIdParam, communityMessageIdParam, communityRoomCreateBody, communityRoomUpdateBody, communityMessagesQuery, communityMessageBodySchema, communityPostTitleSchema, communityAttachmentUploadSchema, communitySendBody, communityEditBody, communityPostIdParam, communityPostsQuery, communityAnswerBody, communityReadBody, communityAttachmentUrlsBody, communityPersonParam, communityOrgParam, communityPeopleQuery, communityCoursesLabelSchema, communityAdmissionSchema, communitySettingsBody, communityAccessEmailSchema, communityAccessAddBody, communityAccessQuery, communityAccessIdParam, communityBanBody, communityLockBody, communityPinBody, communityModLogQuery, communityReportsQuery, communityReportIdParam, communityLiveTicketSchema, communityLiveQuery, communityMuteBody, communityEmailUnsubscribeTokenSchema, COMMUNITY_EMAIL_UNSUBSCRIBE_PATH, communityEmailUnsubscribeQuery, COMMUNITY_COURSES_MAX, COMMUNITY_COURSE_MODULES_MAX, COMMUNITY_COURSE_LESSONS_MAX, COMMUNITY_LESSONS_MAX, COMMUNITY_COURSE_TITLE_MAX_LENGTH, COMMUNITY_COURSE_DESCRIPTION_MAX_LENGTH, COMMUNITY_LESSON_BODY_MAX_LENGTH, COMMUNITY_LESSON_LINKS_MAX, COMMUNITY_LESSON_URL_MAX_LENGTH, YOUTUBE_ID, YOUTUBE_HOSTS, YOUTU_BE, YOUTUBE_ID_PATH, communityCourseIdParam, communityCourseModuleIdParam, communityLessonIdParam, communityCourseTitleSchema, communityCourseDescriptionSchema, communityCoursePositionSchema, communityLessonVideoUrlSchema, communityLessonLinkSchema, communityLessonLinksSchema, communityLessonBodySchema, communityCourseCreateBody, communityCourseUpdateBody, communityModuleCreateBody, communityModuleUpdateBody, communityLessonCreateBody, communityLessonUpdateBody, COMMUNITY_LESSON_TREE_MAX_BYTES, COMMUNITY_LESSON_LINK_PROTOCOLS, COMMUNITY_LESSON_TREE_TOO_LARGE_REASON, COMMUNITY_LESSON_TREE_TOO_DEEP_REASON, COMMUNITY_LESSON_LINE_PREFIX_TOO_WIDE_REASON, LINK_SCHEME, adminSetGuideHubBody, GUIDE_HUB_INELIGIBLE_REASON;
|
|
9793
|
+
var init_chunk_5RGL5RXH = __esm({
|
|
9794
|
+
"../../packages/core/dist/chunk-5RGL5RXH.js"() {
|
|
9790
9795
|
"use strict";
|
|
9791
9796
|
init_chunk_VORPHGDT();
|
|
9792
9797
|
init_chunk_POL7B3EM();
|
|
9793
|
-
|
|
9798
|
+
init_chunk_46WU2TD4();
|
|
9794
9799
|
init_chunk_MIWZOSPI();
|
|
9795
9800
|
init_chunk_3NS3HULK();
|
|
9796
9801
|
init_chunk_JWPRTSIB();
|
|
@@ -10847,7 +10852,8 @@ var init_chunk_GTTWALH3 = __esm({
|
|
|
10847
10852
|
});
|
|
10848
10853
|
updateUserBody = external_exports.object({
|
|
10849
10854
|
user_language: external_exports.string().optional(),
|
|
10850
|
-
|
|
10855
|
+
/** true: dark; false: light; null: follow each device's own setting (`AppearanceMode`). */
|
|
10856
|
+
ui_dark_mode: external_exports.boolean().nullable().optional(),
|
|
10851
10857
|
profile_name: external_exports.string().optional(),
|
|
10852
10858
|
link_email_to_identities: external_exports.boolean().optional()
|
|
10853
10859
|
}).refine(
|
|
@@ -12813,6 +12819,8 @@ var init_chunk_GTTWALH3 = __esm({
|
|
|
12813
12819
|
COMMUNITIES_PER_ORG_MAX = 10;
|
|
12814
12820
|
COMMUNITY_NAME_MAX_LENGTH = 100;
|
|
12815
12821
|
COMMUNITY_REGISTRY_CLOSED_REASON = "community_registry_closed";
|
|
12822
|
+
COMMUNITY_ADMISSIONS = ["link", "invite"];
|
|
12823
|
+
COMMUNITY_ACCESS_ADD_MAX = 500;
|
|
12816
12824
|
COMMUNITY_JOIN_SOURCE_MAX_LENGTH = 32;
|
|
12817
12825
|
COMMUNITY_JOIN_FLOW_TOKEN_MAX_LENGTH = 128;
|
|
12818
12826
|
COMMUNITY_ROOMS_MAX = 20;
|
|
@@ -12873,11 +12881,20 @@ var init_chunk_GTTWALH3 = __esm({
|
|
|
12873
12881
|
communityOrgParam = external_exports.object({ orgId: uuidSchema });
|
|
12874
12882
|
communityPeopleQuery = external_exports.object({ after: peopleUserIdSchema.optional() });
|
|
12875
12883
|
communityCoursesLabelSchema = external_exports.string().trim().max(COMMUNITY_COURSES_LABEL_MAX_LENGTH);
|
|
12884
|
+
communityAdmissionSchema = external_exports.enum(COMMUNITY_ADMISSIONS);
|
|
12876
12885
|
communitySettingsBody = external_exports.object({
|
|
12877
12886
|
rules: external_exports.string().trim().max(COMMUNITY_RULES_MAX_LENGTH).nullable().optional().transform((rules) => rules === "" ? null : rules),
|
|
12878
12887
|
slow_mode_seconds: external_exports.number().int().min(0).max(COMMUNITY_SLOW_MODE_MAX_SECONDS).optional(),
|
|
12879
|
-
courses_label: communityCoursesLabelSchema.optional()
|
|
12880
|
-
|
|
12888
|
+
courses_label: communityCoursesLabelSchema.optional(),
|
|
12889
|
+
admission: communityAdmissionSchema.optional()
|
|
12890
|
+
}).strict().refine(
|
|
12891
|
+
(b) => b.rules !== void 0 || b.slow_mode_seconds !== void 0 || b.courses_label !== void 0 || b.admission !== void 0,
|
|
12892
|
+
"nothing to change"
|
|
12893
|
+
);
|
|
12894
|
+
communityAccessEmailSchema = external_exports.string().trim().toLowerCase().max(CONTACT_EMAIL_MAX).email();
|
|
12895
|
+
communityAccessAddBody = external_exports.object({ emails: external_exports.array(communityAccessEmailSchema).min(1).max(COMMUNITY_ACCESS_ADD_MAX) }).strict();
|
|
12896
|
+
communityAccessQuery = external_exports.object({ cursor: external_exports.string().min(1).max(256).optional() });
|
|
12897
|
+
communityAccessIdParam = external_exports.object({ communityId: uuidSchema, accessId: uuidSchema });
|
|
12881
12898
|
communityBanBody = external_exports.object({ delete_messages: external_exports.boolean().optional() }).strict();
|
|
12882
12899
|
communityLockBody = external_exports.object({ locked: external_exports.boolean() }).strict();
|
|
12883
12900
|
communityPinBody = external_exports.object({ pinned: external_exports.boolean() }).strict();
|
|
@@ -12892,6 +12909,10 @@ var init_chunk_GTTWALH3 = __esm({
|
|
|
12892
12909
|
communityEmailUnsubscribeTokenSchema = external_exports.string().max(200).regex(/^[0-9a-f-]{36}\.[A-Za-z0-9_-]{1,64}\.[0-9a-f]{64}$/i);
|
|
12893
12910
|
COMMUNITY_EMAIL_UNSUBSCRIBE_PATH = "/api/communities/email-unsubscribe";
|
|
12894
12911
|
communityEmailUnsubscribeQuery = external_exports.object({ token: communityEmailUnsubscribeTokenSchema });
|
|
12912
|
+
COMMUNITY_COURSES_MAX = 20;
|
|
12913
|
+
COMMUNITY_COURSE_MODULES_MAX = 20;
|
|
12914
|
+
COMMUNITY_COURSE_LESSONS_MAX = 100;
|
|
12915
|
+
COMMUNITY_LESSONS_MAX = 500;
|
|
12895
12916
|
COMMUNITY_COURSE_TITLE_MAX_LENGTH = 200;
|
|
12896
12917
|
COMMUNITY_COURSE_DESCRIPTION_MAX_LENGTH = 2e3;
|
|
12897
12918
|
COMMUNITY_LESSON_BODY_MAX_LENGTH = 2e4;
|
|
@@ -12952,6 +12973,9 @@ var init_chunk_GTTWALH3 = __esm({
|
|
|
12952
12973
|
}).strict().refine((b) => Object.values(b).some((value) => value !== void 0), "nothing to change");
|
|
12953
12974
|
COMMUNITY_LESSON_TREE_MAX_BYTES = 512 * 1024;
|
|
12954
12975
|
COMMUNITY_LESSON_LINK_PROTOCOLS = ["http", "https", "mailto"];
|
|
12976
|
+
COMMUNITY_LESSON_TREE_TOO_LARGE_REASON = "lesson_tree_too_large";
|
|
12977
|
+
COMMUNITY_LESSON_TREE_TOO_DEEP_REASON = "lesson_tree_too_deep";
|
|
12978
|
+
COMMUNITY_LESSON_LINE_PREFIX_TOO_WIDE_REASON = "lesson_line_prefix_too_wide";
|
|
12955
12979
|
LINK_SCHEME = new RegExp(`^(${COMMUNITY_LESSON_LINK_PROTOCOLS.join("|")}):`, "i");
|
|
12956
12980
|
adminSetGuideHubBody = external_exports.object({
|
|
12957
12981
|
hub_id: uuidSchema,
|
|
@@ -13263,10 +13287,10 @@ var init_chunk_BPDC67ML = __esm({
|
|
|
13263
13287
|
var init_contracts = __esm({
|
|
13264
13288
|
"../../packages/core/dist/contracts/index.js"() {
|
|
13265
13289
|
"use strict";
|
|
13266
|
-
|
|
13290
|
+
init_chunk_5RGL5RXH();
|
|
13267
13291
|
init_chunk_VORPHGDT();
|
|
13268
13292
|
init_chunk_POL7B3EM();
|
|
13269
|
-
|
|
13293
|
+
init_chunk_46WU2TD4();
|
|
13270
13294
|
init_chunk_SNA2DXAC();
|
|
13271
13295
|
init_chunk_MIWZOSPI();
|
|
13272
13296
|
init_chunk_LKBSHANA();
|
|
@@ -13305,10 +13329,10 @@ function toDataProxyPath(v1Path) {
|
|
|
13305
13329
|
}
|
|
13306
13330
|
return `${DATA_PROXY_PREFIX}${v1Path.slice(REKOR_V1_PREFIX.length)}`;
|
|
13307
13331
|
}
|
|
13308
|
-
function withOrgSelector(
|
|
13309
|
-
if (!orgId) return
|
|
13310
|
-
const separator =
|
|
13311
|
-
return `${
|
|
13332
|
+
function withOrgSelector(path41, orgId) {
|
|
13333
|
+
if (!orgId) return path41;
|
|
13334
|
+
const separator = path41.includes("?") ? "&" : "?";
|
|
13335
|
+
return `${path41}${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}`;
|
|
13312
13336
|
}
|
|
13313
13337
|
function dataErrorMessage(err) {
|
|
13314
13338
|
if (!(err instanceof ApiError)) return null;
|
|
@@ -13324,7 +13348,7 @@ function dataErrorMessage(err) {
|
|
|
13324
13348
|
const message = parsed?.error?.message;
|
|
13325
13349
|
return typeof message === "string" && message ? message : null;
|
|
13326
13350
|
}
|
|
13327
|
-
var RETRYABLE_BACKOFF_MS, delay, ApiError, ApiClient;
|
|
13351
|
+
var RETRYABLE_BACKOFF_MS, delay, courseAuthoring, ApiError, ApiClient;
|
|
13328
13352
|
var init_api_client = __esm({
|
|
13329
13353
|
"src/lib/api-client.ts"() {
|
|
13330
13354
|
"use strict";
|
|
@@ -13333,6 +13357,7 @@ var init_api_client = __esm({
|
|
|
13333
13357
|
init_mask_secrets();
|
|
13334
13358
|
RETRYABLE_BACKOFF_MS = [500, 1e3, 2e3];
|
|
13335
13359
|
delay = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
13360
|
+
courseAuthoring = (communityId, courseId) => `/api/communities/${encodeURIComponent(communityId)}/authoring/courses${courseId === void 0 ? "" : `/${encodeURIComponent(courseId)}`}`;
|
|
13336
13361
|
ApiError = class extends Error {
|
|
13337
13362
|
status;
|
|
13338
13363
|
body;
|
|
@@ -13342,13 +13367,13 @@ var init_api_client = __esm({
|
|
|
13342
13367
|
* telling them apart by body shape misreads one for the other.
|
|
13343
13368
|
*/
|
|
13344
13369
|
path;
|
|
13345
|
-
constructor(method,
|
|
13370
|
+
constructor(method, path41, status, body) {
|
|
13346
13371
|
const safeBody = maskSecretsInMessage(body);
|
|
13347
|
-
super(`API request failed: ${method} ${
|
|
13372
|
+
super(`API request failed: ${method} ${path41} (${status}): ${safeBody}`);
|
|
13348
13373
|
this.name = "ApiError";
|
|
13349
13374
|
this.status = status;
|
|
13350
13375
|
this.body = safeBody;
|
|
13351
|
-
this.path =
|
|
13376
|
+
this.path = path41;
|
|
13352
13377
|
}
|
|
13353
13378
|
/** True when the status code is a 4xx client error (expected user-facing condition, not a bug). */
|
|
13354
13379
|
get isExpected() {
|
|
@@ -13492,6 +13517,33 @@ var init_api_client = __esm({
|
|
|
13492
13517
|
body
|
|
13493
13518
|
);
|
|
13494
13519
|
}
|
|
13520
|
+
// A community's courses, which `wayai pull`/`push communities/<x>` sync (gh #5003): every course,
|
|
13521
|
+
// drafts included; a course whole in one read (its export); and the authoring writes. A create
|
|
13522
|
+
// with a `client_id` already used answers the course, module or lesson made then, and makes none.
|
|
13523
|
+
async listCommunityCourseDrafts(communityId) {
|
|
13524
|
+
return this.request("GET", courseAuthoring(communityId));
|
|
13525
|
+
}
|
|
13526
|
+
async exportCommunityCourse(communityId, courseId) {
|
|
13527
|
+
return this.request("GET", `${courseAuthoring(communityId, courseId)}/export`);
|
|
13528
|
+
}
|
|
13529
|
+
async createCommunityCourse(communityId, body) {
|
|
13530
|
+
return this.request("POST", courseAuthoring(communityId), body);
|
|
13531
|
+
}
|
|
13532
|
+
async updateCommunityCourse(communityId, courseId, body) {
|
|
13533
|
+
return this.request("PATCH", courseAuthoring(communityId, courseId), body);
|
|
13534
|
+
}
|
|
13535
|
+
async createCommunityModule(communityId, courseId, body) {
|
|
13536
|
+
return this.request("POST", `${courseAuthoring(communityId, courseId)}/modules`, body);
|
|
13537
|
+
}
|
|
13538
|
+
async updateCommunityModule(communityId, courseId, moduleId, body) {
|
|
13539
|
+
return this.request("PATCH", `${courseAuthoring(communityId, courseId)}/modules/${encodeURIComponent(moduleId)}`, body);
|
|
13540
|
+
}
|
|
13541
|
+
async createCommunityLesson(communityId, courseId, body) {
|
|
13542
|
+
return this.request("POST", `${courseAuthoring(communityId, courseId)}/lessons`, body);
|
|
13543
|
+
}
|
|
13544
|
+
async updateCommunityLesson(communityId, courseId, lessonId, body) {
|
|
13545
|
+
return this.request("PATCH", `${courseAuthoring(communityId, courseId)}/lessons/${encodeURIComponent(lessonId)}`, body);
|
|
13546
|
+
}
|
|
13495
13547
|
// Read which hub is the WayAI Guide (platform-admin gated, wayai#4948). Used by
|
|
13496
13548
|
// `wayai admin guide status`.
|
|
13497
13549
|
async adminGetGuide() {
|
|
@@ -13520,8 +13572,8 @@ var init_api_client = __esm({
|
|
|
13520
13572
|
...opts?.check && { check: true }
|
|
13521
13573
|
});
|
|
13522
13574
|
}
|
|
13523
|
-
async lookup(
|
|
13524
|
-
const params = new URLSearchParams({ path:
|
|
13575
|
+
async lookup(path41, opts) {
|
|
13576
|
+
const params = new URLSearchParams({ path: path41 });
|
|
13525
13577
|
if (opts?.organizationId) params.set("organization_id", opts.organizationId);
|
|
13526
13578
|
return this.request("GET", `/api/ci/lookup?${params.toString()}`);
|
|
13527
13579
|
}
|
|
@@ -14029,9 +14081,9 @@ var init_api_client = __esm({
|
|
|
14029
14081
|
* sandbox for this conversation, or the blob was purged).
|
|
14030
14082
|
*/
|
|
14031
14083
|
async downloadArchiveSandboxFs(hubId, conversationId) {
|
|
14032
|
-
const
|
|
14033
|
-
addApiBreadcrumb("GET",
|
|
14034
|
-
const url = `${this.apiUrl}${
|
|
14084
|
+
const path41 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
|
|
14085
|
+
addApiBreadcrumb("GET", path41);
|
|
14086
|
+
const url = `${this.apiUrl}${path41}`;
|
|
14035
14087
|
let response = await this.send(url, "GET");
|
|
14036
14088
|
if (response.status === 401 && this.onUnauthorized) {
|
|
14037
14089
|
let refreshed;
|
|
@@ -14045,7 +14097,7 @@ var init_api_client = __esm({
|
|
|
14045
14097
|
}
|
|
14046
14098
|
}
|
|
14047
14099
|
if (!response.ok) {
|
|
14048
|
-
throw new ApiError("GET",
|
|
14100
|
+
throw new ApiError("GET", path41, response.status, await response.text());
|
|
14049
14101
|
}
|
|
14050
14102
|
return new Uint8Array(await response.arrayBuffer());
|
|
14051
14103
|
}
|
|
@@ -14091,9 +14143,9 @@ var init_api_client = __esm({
|
|
|
14091
14143
|
`/api/admin/data-explorer/debug/observability/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}${qs}`
|
|
14092
14144
|
);
|
|
14093
14145
|
}
|
|
14094
|
-
async request(method,
|
|
14095
|
-
addApiBreadcrumb(method,
|
|
14096
|
-
const url = `${this.apiUrl}${
|
|
14146
|
+
async request(method, path41, body, extraHeaders, contentType) {
|
|
14147
|
+
addApiBreadcrumb(method, path41);
|
|
14148
|
+
const url = `${this.apiUrl}${path41}`;
|
|
14097
14149
|
let refreshedOn401 = false;
|
|
14098
14150
|
for (let retry = 0; ; retry++) {
|
|
14099
14151
|
let response = await this.send(url, method, body, extraHeaders, contentType);
|
|
@@ -14118,7 +14170,7 @@ var init_api_client = __esm({
|
|
|
14118
14170
|
await delay(RETRYABLE_BACKOFF_MS[retry]);
|
|
14119
14171
|
continue;
|
|
14120
14172
|
}
|
|
14121
|
-
throw new ApiError(method,
|
|
14173
|
+
throw new ApiError(method, path41, response.status, errorBody);
|
|
14122
14174
|
}
|
|
14123
14175
|
}
|
|
14124
14176
|
/**
|
|
@@ -14180,9 +14232,9 @@ var init_api_client = __esm({
|
|
|
14180
14232
|
* transient-retry loop — re-streaming a blob is not worth a cold-start probe.
|
|
14181
14233
|
*/
|
|
14182
14234
|
async dataDownload(v1Path, orgId) {
|
|
14183
|
-
const
|
|
14184
|
-
addApiBreadcrumb("GET",
|
|
14185
|
-
const url = `${this.apiUrl}${
|
|
14235
|
+
const path41 = withOrgSelector(toDataProxyPath(v1Path), orgId);
|
|
14236
|
+
addApiBreadcrumb("GET", path41);
|
|
14237
|
+
const url = `${this.apiUrl}${path41}`;
|
|
14186
14238
|
let response = await this.send(url, "GET");
|
|
14187
14239
|
if (response.status === 401 && this.onUnauthorized) {
|
|
14188
14240
|
let refreshed;
|
|
@@ -14196,7 +14248,7 @@ var init_api_client = __esm({
|
|
|
14196
14248
|
}
|
|
14197
14249
|
}
|
|
14198
14250
|
if (!response.ok) {
|
|
14199
|
-
throw new ApiError("GET",
|
|
14251
|
+
throw new ApiError("GET", path41, response.status, await response.text());
|
|
14200
14252
|
}
|
|
14201
14253
|
return {
|
|
14202
14254
|
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
@@ -14267,6 +14319,15 @@ function extractApiErrorCode(err) {
|
|
|
14267
14319
|
return null;
|
|
14268
14320
|
}
|
|
14269
14321
|
}
|
|
14322
|
+
function extractApiErrorReason(err) {
|
|
14323
|
+
if (!(err instanceof ApiError)) return null;
|
|
14324
|
+
try {
|
|
14325
|
+
const reason = JSON.parse(err.body).details?.reason;
|
|
14326
|
+
return typeof reason === "string" ? reason : null;
|
|
14327
|
+
} catch {
|
|
14328
|
+
return null;
|
|
14329
|
+
}
|
|
14330
|
+
}
|
|
14270
14331
|
function extractApiRetryAfterSeconds(err) {
|
|
14271
14332
|
if (!(err instanceof ApiError)) return null;
|
|
14272
14333
|
try {
|
|
@@ -14433,10 +14494,10 @@ var init_dist = __esm({
|
|
|
14433
14494
|
"../../packages/core/dist/index.js"() {
|
|
14434
14495
|
"use strict";
|
|
14435
14496
|
init_chunk_WSROLRJN();
|
|
14436
|
-
|
|
14497
|
+
init_chunk_5RGL5RXH();
|
|
14437
14498
|
init_chunk_VORPHGDT();
|
|
14438
14499
|
init_chunk_POL7B3EM();
|
|
14439
|
-
|
|
14500
|
+
init_chunk_46WU2TD4();
|
|
14440
14501
|
init_chunk_SNA2DXAC();
|
|
14441
14502
|
init_chunk_MIWZOSPI();
|
|
14442
14503
|
init_chunk_LKBSHANA();
|
|
@@ -15106,6 +15167,12 @@ function writeFileNoFollow(root, abs, data) {
|
|
|
15106
15167
|
fs.writeFileSync(abs, data);
|
|
15107
15168
|
return true;
|
|
15108
15169
|
}
|
|
15170
|
+
function writeFileIfChangedNoFollow(root, abs, content) {
|
|
15171
|
+
const bytes = Buffer.from(content, "utf-8");
|
|
15172
|
+
if (fileHasBytes(abs, bytes)) return false;
|
|
15173
|
+
if (!writeFileNoFollow(root, abs, bytes)) throw symlinkRefusal(path.relative(path.dirname(root), abs), "file");
|
|
15174
|
+
return true;
|
|
15175
|
+
}
|
|
15109
15176
|
function createFileNoFollow(root, abs, data) {
|
|
15110
15177
|
if (!ensureRealSubdirNoSymlink(root, path.dirname(abs), true)) return "refused";
|
|
15111
15178
|
try {
|
|
@@ -15598,14 +15665,14 @@ function readOrgFields(doc) {
|
|
|
15598
15665
|
if (name) config.organization_name = name;
|
|
15599
15666
|
return { kind: "declared", config };
|
|
15600
15667
|
}
|
|
15601
|
-
function declarationFrom(
|
|
15602
|
-
if (
|
|
15603
|
-
if (
|
|
15604
|
-
return readOrgFields(
|
|
15668
|
+
function declarationFrom(load14) {
|
|
15669
|
+
if (load14.kind === "absent") return { kind: "none" };
|
|
15670
|
+
if (load14.kind === "malformed") return { kind: "invalid", detail: `it does not parse (${load14.reason})` };
|
|
15671
|
+
return readOrgFields(load14.doc);
|
|
15605
15672
|
}
|
|
15606
|
-
function legacyFieldsIn(
|
|
15607
|
-
if (
|
|
15608
|
-
return LEGACY_FIELDS.filter((f) =>
|
|
15673
|
+
function legacyFieldsIn(load14) {
|
|
15674
|
+
if (load14.kind !== "ok") return [];
|
|
15675
|
+
return LEGACY_FIELDS.filter((f) => load14.doc[f] !== void 0);
|
|
15609
15676
|
}
|
|
15610
15677
|
function resolve3(gitRoot) {
|
|
15611
15678
|
const manifestFile = workspaceManifestPath(gitRoot);
|
|
@@ -15791,13 +15858,13 @@ function writeRepoConfig(config, root) {
|
|
|
15791
15858
|
if (stale.kind === "declared" && stale.config.organization_id !== config.organization_id) {
|
|
15792
15859
|
throw expected(rebindRefusal(config.organization_id, stale.config.organization_id));
|
|
15793
15860
|
}
|
|
15794
|
-
const
|
|
15795
|
-
if (
|
|
15861
|
+
const load14 = loadWorkspaceManifest(gitRoot);
|
|
15862
|
+
if (load14.kind === "malformed") {
|
|
15796
15863
|
throw expected(
|
|
15797
|
-
`Cannot write the organization binding: ${WORKSPACE_MANIFEST_LABEL} does not parse (${
|
|
15864
|
+
`Cannot write the organization binding: ${WORKSPACE_MANIFEST_LABEL} does not parse (${load14.reason}). Fix or delete it, then re-run.`
|
|
15798
15865
|
);
|
|
15799
15866
|
}
|
|
15800
|
-
const { organization_id: _id, organization_name: _name, ...rest } =
|
|
15867
|
+
const { organization_id: _id, organization_name: _name, ...rest } = load14.kind === "ok" ? load14.doc : {};
|
|
15801
15868
|
const doc = { organization_id: config.organization_id };
|
|
15802
15869
|
if (config.organization_name?.trim()) doc.organization_name = config.organization_name.trim();
|
|
15803
15870
|
Object.assign(doc, rest);
|
|
@@ -16355,9 +16422,9 @@ function getVersionCachePath(filename = CLI_CACHE_FILE) {
|
|
|
16355
16422
|
}
|
|
16356
16423
|
function readVersionCache(filename = CLI_CACHE_FILE) {
|
|
16357
16424
|
try {
|
|
16358
|
-
const
|
|
16359
|
-
if (!existsSync3(
|
|
16360
|
-
const parsed = JSON.parse(readFileSync5(
|
|
16425
|
+
const path41 = getVersionCachePath(filename);
|
|
16426
|
+
if (!existsSync3(path41)) return null;
|
|
16427
|
+
const parsed = JSON.parse(readFileSync5(path41, "utf-8"));
|
|
16361
16428
|
if (typeof parsed.lastCheck !== "number") return null;
|
|
16362
16429
|
if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
|
|
16363
16430
|
return parsed;
|
|
@@ -16377,10 +16444,10 @@ function isVersionCacheStale(filename = CLI_CACHE_FILE, maxAgeMs = MAX_AGE_24H_M
|
|
|
16377
16444
|
return Date.now() - cache.lastCheck > maxAgeMs;
|
|
16378
16445
|
}
|
|
16379
16446
|
function writeVersionCache(filename, cache) {
|
|
16380
|
-
const
|
|
16381
|
-
const dir = dirname6(
|
|
16447
|
+
const path41 = getVersionCachePath(filename);
|
|
16448
|
+
const dir = dirname6(path41);
|
|
16382
16449
|
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
16383
|
-
writeFileSync2(
|
|
16450
|
+
writeFileSync2(path41, JSON.stringify(cache));
|
|
16384
16451
|
}
|
|
16385
16452
|
function touchVersionCache(filename) {
|
|
16386
16453
|
writeVersionCache(filename, { lastCheck: Date.now(), latest: readVersionCache(filename)?.latest ?? null });
|
|
@@ -16419,14 +16486,14 @@ function parseFrontmatterVersion(content) {
|
|
|
16419
16486
|
function findInstalledSkills(projectRoot, paths = SKILL_INSTALL_PATHS) {
|
|
16420
16487
|
const found = [];
|
|
16421
16488
|
for (const rel of paths) {
|
|
16422
|
-
const
|
|
16423
|
-
if (!existsSync4(
|
|
16489
|
+
const path41 = join8(projectRoot, rel);
|
|
16490
|
+
if (!existsSync4(path41)) continue;
|
|
16424
16491
|
let version = null;
|
|
16425
16492
|
try {
|
|
16426
|
-
version = parseFrontmatterVersion(readFileSync6(
|
|
16493
|
+
version = parseFrontmatterVersion(readFileSync6(path41, "utf-8"));
|
|
16427
16494
|
} catch {
|
|
16428
16495
|
}
|
|
16429
|
-
found.push({ path:
|
|
16496
|
+
found.push({ path: path41, version });
|
|
16430
16497
|
}
|
|
16431
16498
|
return found;
|
|
16432
16499
|
}
|
|
@@ -17958,18 +18025,22 @@ var init_init = __esm({
|
|
|
17958
18025
|
import * as fs9 from "fs";
|
|
17959
18026
|
import * as path11 from "path";
|
|
17960
18027
|
import * as yaml6 from "js-yaml";
|
|
17961
|
-
function
|
|
17962
|
-
|
|
18028
|
+
function uuidField(doc, key) {
|
|
18029
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
|
18030
|
+
const id = doc[key];
|
|
18031
|
+
return typeof id === "string" && UUID_RE3.test(id) ? id : null;
|
|
18032
|
+
}
|
|
18033
|
+
function readYamlIdNoFollow(root, file, key) {
|
|
18034
|
+
const bytes = readFileNoFollow(root, file);
|
|
17963
18035
|
if (bytes === null) return null;
|
|
17964
|
-
let doc;
|
|
17965
18036
|
try {
|
|
17966
|
-
|
|
18037
|
+
return uuidField(yaml6.load(bytes.toString("utf-8")), key);
|
|
17967
18038
|
} catch {
|
|
17968
18039
|
return null;
|
|
17969
18040
|
}
|
|
17970
|
-
|
|
17971
|
-
|
|
17972
|
-
return
|
|
18041
|
+
}
|
|
18042
|
+
function readCommunityId(folder) {
|
|
18043
|
+
return readYamlIdNoFollow(folder, path11.join(folder, COMMUNITY_FILE), "community_id");
|
|
17973
18044
|
}
|
|
17974
18045
|
function listCommunityFolders(communitiesDir) {
|
|
17975
18046
|
let entries;
|
|
@@ -19607,9 +19678,9 @@ var init_base_workspace = __esm({
|
|
|
19607
19678
|
// src/lib/subtree-routing.ts
|
|
19608
19679
|
import * as path20 from "path";
|
|
19609
19680
|
function readRepoDefaults(gitRoot) {
|
|
19610
|
-
const
|
|
19611
|
-
if (
|
|
19612
|
-
const raw =
|
|
19681
|
+
const load14 = loadWorkspaceManifest(gitRoot);
|
|
19682
|
+
if (load14.kind !== "ok") return {};
|
|
19683
|
+
const raw = load14.doc;
|
|
19613
19684
|
const str = (v) => {
|
|
19614
19685
|
if (typeof v !== "string" || !v.trim()) return void 0;
|
|
19615
19686
|
const value = v.trim();
|
|
@@ -19636,7 +19707,13 @@ function parseRoutingTokens(args2) {
|
|
|
19636
19707
|
if (name === "--base") tokens2.baseFlag ??= value;
|
|
19637
19708
|
if (name === "--community") tokens2.communityFlag ??= value;
|
|
19638
19709
|
if (name === "--org") tokens2.orgFlag ??= value;
|
|
19639
|
-
if (eq === -1)
|
|
19710
|
+
if (eq === -1) {
|
|
19711
|
+
const taken = value.split("=")[0];
|
|
19712
|
+
if (SUBTREE_FLAGS.some(({ flag }) => flag === taken)) tokens2.takenAsValue ??= { flag: taken, by: name };
|
|
19713
|
+
i++;
|
|
19714
|
+
}
|
|
19715
|
+
} else {
|
|
19716
|
+
tokens2.trailingFlag = name;
|
|
19640
19717
|
}
|
|
19641
19718
|
continue;
|
|
19642
19719
|
}
|
|
@@ -19705,31 +19782,42 @@ function hubAndBaseRefusal(verb, hubTarget, baseTarget) {
|
|
|
19705
19782
|
]);
|
|
19706
19783
|
}
|
|
19707
19784
|
function subtreeFlags(tokens2) {
|
|
19708
|
-
|
|
19709
|
-
|
|
19710
|
-
|
|
19711
|
-
|
|
19712
|
-
return flags;
|
|
19785
|
+
return SUBTREE_FLAGS.flatMap(({ token, subtree }) => {
|
|
19786
|
+
const target = tokens2[token];
|
|
19787
|
+
return target === void 0 ? [] : [{ subtree, target }];
|
|
19788
|
+
});
|
|
19713
19789
|
}
|
|
19714
19790
|
function routeSubtree(options) {
|
|
19715
19791
|
const result = routeToSubtree(options);
|
|
19716
19792
|
if (!result.ok || result.subtree === "communities") return result;
|
|
19717
19793
|
const communityOnly = (COMMUNITY_ONLY_FLAGS[options.verb] ?? []).find((flag) => options.args.includes(flag));
|
|
19718
|
-
if (communityOnly
|
|
19719
|
-
|
|
19720
|
-
|
|
19721
|
-
|
|
19722
|
-
|
|
19723
|
-
|
|
19724
|
-
|
|
19725
|
-
|
|
19726
|
-
|
|
19727
|
-
|
|
19794
|
+
if (communityOnly !== void 0) {
|
|
19795
|
+
return {
|
|
19796
|
+
ok: false,
|
|
19797
|
+
refusal: [
|
|
19798
|
+
// States only what holds for every flag and verb in the table: this target does not read the
|
|
19799
|
+
// flag, and a community's pull does (`--dry-run` is also read by some pushes, `--theirs` by none).
|
|
19800
|
+
`\`wayai ${options.verb}\` of ${SUBDIR[result.subtree]}/ does not read \`${communityOnly}\`, so it is refused rather than run without it. A community's pull takes it:`,
|
|
19801
|
+
` wayai pull ${SUBDIR.communities}/<folder> ${communityOnly}`
|
|
19802
|
+
].join("\n")
|
|
19803
|
+
};
|
|
19804
|
+
}
|
|
19805
|
+
if (options.verb === "push" && result.subtree === "hubs" && options.args.includes("--dry-run")) {
|
|
19806
|
+
return {
|
|
19807
|
+
ok: false,
|
|
19808
|
+
refusal: [
|
|
19809
|
+
`\`wayai push\` of ${SUBDIR.hubs}/ does not read \`--dry-run\`, so it is refused rather than run without it. For a hub already created (a \`hub_id\` in its hub.yaml), \`wayai diff\` lists the changes without pushing them:`,
|
|
19810
|
+
" wayai diff --hub <uuid|folder-name>"
|
|
19811
|
+
].join("\n")
|
|
19812
|
+
};
|
|
19813
|
+
}
|
|
19814
|
+
return result;
|
|
19728
19815
|
}
|
|
19729
19816
|
function routeToSubtree(options) {
|
|
19730
19817
|
const { verb, args: args2, gitRoot } = options;
|
|
19731
19818
|
const cwd = options.cwd ?? process.cwd();
|
|
19732
|
-
const
|
|
19819
|
+
const tokens2 = parseRoutingTokens(args2);
|
|
19820
|
+
const { hubFlag, baseFlag, communityFlag, positional, orgFlag } = tokens2;
|
|
19733
19821
|
if (orgFlag !== void 0) {
|
|
19734
19822
|
return {
|
|
19735
19823
|
ok: false,
|
|
@@ -19742,18 +19830,26 @@ function routeToSubtree(options) {
|
|
|
19742
19830
|
].join("\n")
|
|
19743
19831
|
};
|
|
19744
19832
|
}
|
|
19745
|
-
|
|
19746
|
-
|
|
19747
|
-
|
|
19748
|
-
|
|
19833
|
+
if (tokens2.takenAsValue) {
|
|
19834
|
+
const { flag: flag2, by } = tokens2.takenAsValue;
|
|
19835
|
+
return {
|
|
19836
|
+
ok: false,
|
|
19837
|
+
refusal: `\`wayai ${verb} ${by}\` took \`${flag2}\` as its value, so \`${flag2}\` is not read. Give ${by} a value, or leave it out.`
|
|
19838
|
+
};
|
|
19749
19839
|
}
|
|
19750
|
-
|
|
19840
|
+
const empty = SUBTREE_FLAGS.find(({ flag: flag2, token }) => tokens2[token] === "" || tokens2.trailingFlag === flag2);
|
|
19841
|
+
if (empty) {
|
|
19751
19842
|
return {
|
|
19752
19843
|
ok: false,
|
|
19753
|
-
refusal: `\`wayai ${verb}
|
|
19754
|
-
wayai ${verb}
|
|
19844
|
+
refusal: `\`wayai ${verb} ${empty.flag}\` needs ${empty.target}, and none followed it.
|
|
19845
|
+
wayai ${verb} ${empty.flag} ${empty.value}`
|
|
19755
19846
|
};
|
|
19756
19847
|
}
|
|
19848
|
+
const layout = gitRoot ? resolveLayout(gitRoot) : null;
|
|
19849
|
+
const flagged = subtreeFlags({ hubFlag, baseFlag, communityFlag });
|
|
19850
|
+
if (flagged.length > 1) {
|
|
19851
|
+
return { ok: false, refusal: mixedInvocationRefusal(verb, flagged) };
|
|
19852
|
+
}
|
|
19757
19853
|
const fromPositional = positional !== void 0 ? resolveSelectorSubtree(layout, positional, cwd) : void 0;
|
|
19758
19854
|
if (fromPositional?.kind === "both") {
|
|
19759
19855
|
return {
|
|
@@ -19812,7 +19908,7 @@ function requireSubtree(verb, args2, gitRoot) {
|
|
|
19812
19908
|
}
|
|
19813
19909
|
return { subtree: result.subtree, selector: result.selector };
|
|
19814
19910
|
}
|
|
19815
|
-
var VALUE_FLAGS, COMMUNITY_ONLY_FLAGS, SUBDIR;
|
|
19911
|
+
var VALUE_FLAGS, SUBTREE_FLAGS, COMMUNITY_ONLY_FLAGS, SUBDIR;
|
|
19816
19912
|
var init_subtree_routing = __esm({
|
|
19817
19913
|
"src/lib/subtree-routing.ts"() {
|
|
19818
19914
|
"use strict";
|
|
@@ -19822,6 +19918,11 @@ var init_subtree_routing = __esm({
|
|
|
19822
19918
|
init_workspace();
|
|
19823
19919
|
init_base_workspace();
|
|
19824
19920
|
VALUE_FLAGS = /* @__PURE__ */ new Set(["--hub", "--base", "--community", "--label", "--org"]);
|
|
19921
|
+
SUBTREE_FLAGS = [
|
|
19922
|
+
{ flag: "--hub", token: "hubFlag", subtree: "hubs", target: "a hub", value: "<uuid|folder-name>" },
|
|
19923
|
+
{ flag: "--base", token: "baseFlag", subtree: "bases", target: "a base", value: "<id|folder-name>" },
|
|
19924
|
+
{ flag: "--community", token: "communityFlag", subtree: "communities", target: "a community", value: "<folder|community_id>" }
|
|
19925
|
+
];
|
|
19825
19926
|
COMMUNITY_ONLY_FLAGS = { pull: ["--theirs", "--dry-run"], push: ["--theirs"] };
|
|
19826
19927
|
SUBDIR = {
|
|
19827
19928
|
hubs: WAYAI_LAYOUT.hubsSubdir,
|
|
@@ -19864,12 +19965,12 @@ async function createDataClient(orgId) {
|
|
|
19864
19965
|
return orgId2;
|
|
19865
19966
|
}
|
|
19866
19967
|
return {
|
|
19867
|
-
async request(method,
|
|
19868
|
-
const envelope = await api.dataRequest(method,
|
|
19968
|
+
async request(method, path41, body) {
|
|
19969
|
+
const envelope = await api.dataRequest(method, path41, body, org);
|
|
19869
19970
|
return envelope.data;
|
|
19870
19971
|
},
|
|
19871
|
-
async requestRaw(method,
|
|
19872
|
-
const body_ = await api.dataRequest(method,
|
|
19972
|
+
async requestRaw(method, path41, body) {
|
|
19973
|
+
const body_ = await api.dataRequest(method, path41, body, org);
|
|
19873
19974
|
return body_;
|
|
19874
19975
|
},
|
|
19875
19976
|
async collectPages(makePath) {
|
|
@@ -20713,6 +20814,133 @@ var init_sync = __esm({
|
|
|
20713
20814
|
}
|
|
20714
20815
|
});
|
|
20715
20816
|
|
|
20817
|
+
// src/lib/eval-session-control.ts
|
|
20818
|
+
function formatEvalSessionStopCommand(sessionId, hubId) {
|
|
20819
|
+
return `wayai eval session stop ${sessionId} --hub ${hubId}`;
|
|
20820
|
+
}
|
|
20821
|
+
function formatEvalResultsCommand(sessionId, hubId) {
|
|
20822
|
+
return `wayai eval-results --session ${sessionId} --hub ${hubId}`;
|
|
20823
|
+
}
|
|
20824
|
+
function describeSeedRelease(status) {
|
|
20825
|
+
if (status === "pending") {
|
|
20826
|
+
return "A turn that started before the stop is still finishing \u2014 its fixture stays reserved until that turn settles.";
|
|
20827
|
+
}
|
|
20828
|
+
if (status === "releasing") return "Its fixture is being released.";
|
|
20829
|
+
return "A turn already in progress may still be finishing \u2014 wait for it before starting another run.";
|
|
20830
|
+
}
|
|
20831
|
+
function classifyStopOutcome(response) {
|
|
20832
|
+
return response.data.stopped ? { kind: "cancelled", seedRelease: response.data.seed_release_status } : { kind: "already_finished", status: response.data.session.session_status };
|
|
20833
|
+
}
|
|
20834
|
+
function isTerminalStopFailure(err) {
|
|
20835
|
+
return err instanceof ApiError && err.status >= 400 && err.status < 500 && err.status !== 408 && err.status !== 429;
|
|
20836
|
+
}
|
|
20837
|
+
async function stopEvalSessionWithRetries(stop, opts = {}) {
|
|
20838
|
+
const attempts = opts.attempts ?? STOP_ATTEMPTS;
|
|
20839
|
+
const attemptTimeoutMs = opts.attemptTimeoutMs ?? STOP_ATTEMPT_TIMEOUT_MS;
|
|
20840
|
+
const backoffMs = opts.backoffMs ?? STOP_BACKOFF_MS;
|
|
20841
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
20842
|
+
let lastError = new Error("Stop was never attempted");
|
|
20843
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
20844
|
+
let timeout;
|
|
20845
|
+
try {
|
|
20846
|
+
return await Promise.race([
|
|
20847
|
+
stop(),
|
|
20848
|
+
new Promise((_resolve, reject) => {
|
|
20849
|
+
timeout = setTimeout(
|
|
20850
|
+
() => reject(new Error(`Stop attempt ${attempt} timed out after ${attemptTimeoutMs}ms`)),
|
|
20851
|
+
attemptTimeoutMs
|
|
20852
|
+
);
|
|
20853
|
+
})
|
|
20854
|
+
]);
|
|
20855
|
+
} catch (err) {
|
|
20856
|
+
lastError = err;
|
|
20857
|
+
if (isTerminalStopFailure(err)) break;
|
|
20858
|
+
} finally {
|
|
20859
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
20860
|
+
}
|
|
20861
|
+
const delay2 = backoffMs[attempt - 1];
|
|
20862
|
+
if (attempt < attempts && delay2) await sleep3(delay2);
|
|
20863
|
+
}
|
|
20864
|
+
throw lastError;
|
|
20865
|
+
}
|
|
20866
|
+
function installEvalSignalHandlers(input) {
|
|
20867
|
+
const exit = input.exit ?? ((code) => process.exit(code));
|
|
20868
|
+
const manualStopCommand = formatEvalSessionStopCommand(input.sessionId, input.hubId);
|
|
20869
|
+
let signalCount = 0;
|
|
20870
|
+
let disposed = false;
|
|
20871
|
+
let settle;
|
|
20872
|
+
const settled = new Promise((resolve12) => {
|
|
20873
|
+
settle = resolve12;
|
|
20874
|
+
});
|
|
20875
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
20876
|
+
const dispose = () => {
|
|
20877
|
+
if (disposed) return;
|
|
20878
|
+
disposed = true;
|
|
20879
|
+
for (const [signal, listener] of listeners) process.off(signal, listener);
|
|
20880
|
+
};
|
|
20881
|
+
const finish = (exitCode) => {
|
|
20882
|
+
dispose();
|
|
20883
|
+
settle();
|
|
20884
|
+
exit(exitCode);
|
|
20885
|
+
};
|
|
20886
|
+
const handle = (signal) => {
|
|
20887
|
+
signalCount += 1;
|
|
20888
|
+
const signalExitCode = SIGNAL_EXIT_CODE[signal];
|
|
20889
|
+
if (signalCount > 1) {
|
|
20890
|
+
console.error("\nSecond signal received; exiting immediately. The remote eval session may still be running.");
|
|
20891
|
+
console.error(`Run: ${manualStopCommand}`);
|
|
20892
|
+
finish(signalExitCode);
|
|
20893
|
+
return;
|
|
20894
|
+
}
|
|
20895
|
+
console.error(`
|
|
20896
|
+
${signal} received; stopping remote eval session ${input.sessionId}...`);
|
|
20897
|
+
void stopEvalSessionWithRetries(input.stop).then((response) => {
|
|
20898
|
+
if (signalCount > 1) return;
|
|
20899
|
+
const outcome = classifyStopOutcome(response);
|
|
20900
|
+
if (outcome.kind === "already_finished") {
|
|
20901
|
+
console.error(`Session had already finished (${outcome.status}) \u2014 nothing was cancelled.`);
|
|
20902
|
+
console.error(`See its results: ${formatEvalResultsCommand(input.sessionId, input.hubId)}`);
|
|
20903
|
+
} else {
|
|
20904
|
+
console.error("Remote session cancellation requested.");
|
|
20905
|
+
console.error(describeSeedRelease(outcome.seedRelease));
|
|
20906
|
+
}
|
|
20907
|
+
finish(signalExitCode);
|
|
20908
|
+
}).catch((err) => {
|
|
20909
|
+
if (signalCount > 1) return;
|
|
20910
|
+
console.error(`Failed to confirm remote cancellation: ${extractApiMessage(err)}`);
|
|
20911
|
+
console.error(`The session may still be running. Run: ${manualStopCommand}`);
|
|
20912
|
+
finish(1);
|
|
20913
|
+
});
|
|
20914
|
+
};
|
|
20915
|
+
for (const signal of HANDLED_SIGNALS) {
|
|
20916
|
+
const listener = () => handle(signal);
|
|
20917
|
+
listeners.set(signal, listener);
|
|
20918
|
+
process.on(signal, listener);
|
|
20919
|
+
}
|
|
20920
|
+
return {
|
|
20921
|
+
manualStopCommand,
|
|
20922
|
+
async interrupted() {
|
|
20923
|
+
if (signalCount === 0) return false;
|
|
20924
|
+
await settled;
|
|
20925
|
+
return true;
|
|
20926
|
+
},
|
|
20927
|
+
dispose
|
|
20928
|
+
};
|
|
20929
|
+
}
|
|
20930
|
+
var SIGNAL_EXIT_CODE, HANDLED_SIGNALS, STOP_ATTEMPTS, STOP_ATTEMPT_TIMEOUT_MS, STOP_BACKOFF_MS;
|
|
20931
|
+
var init_eval_session_control = __esm({
|
|
20932
|
+
"src/lib/eval-session-control.ts"() {
|
|
20933
|
+
"use strict";
|
|
20934
|
+
init_api_client();
|
|
20935
|
+
init_errors2();
|
|
20936
|
+
SIGNAL_EXIT_CODE = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 };
|
|
20937
|
+
HANDLED_SIGNALS = Object.keys(SIGNAL_EXIT_CODE);
|
|
20938
|
+
STOP_ATTEMPTS = 3;
|
|
20939
|
+
STOP_ATTEMPT_TIMEOUT_MS = 1e4;
|
|
20940
|
+
STOP_BACKOFF_MS = [500, 1e3];
|
|
20941
|
+
}
|
|
20942
|
+
});
|
|
20943
|
+
|
|
20716
20944
|
// src/communities/api.ts
|
|
20717
20945
|
async function communitiesClient() {
|
|
20718
20946
|
const { config, accessToken } = await requireAuth();
|
|
@@ -20845,6 +21073,23 @@ function settingsContent(settings) {
|
|
|
20845
21073
|
function roomContent(room) {
|
|
20846
21074
|
return { name: toLf(room.name).trim(), archived: room.archived };
|
|
20847
21075
|
}
|
|
21076
|
+
function courseContent(course) {
|
|
21077
|
+
return { title: toLf(course.title).trim(), description: toLf(course.description).trim(), published: course.published };
|
|
21078
|
+
}
|
|
21079
|
+
function moduleContent(module) {
|
|
21080
|
+
return { title: toLf(module.title).trim() };
|
|
21081
|
+
}
|
|
21082
|
+
function lessonContent(lesson) {
|
|
21083
|
+
return {
|
|
21084
|
+
title: toLf(lesson.title).trim(),
|
|
21085
|
+
body: toLf(lesson.body),
|
|
21086
|
+
video: lesson.video ? { provider: lesson.video.provider, id: lesson.video.id } : null,
|
|
21087
|
+
links: lesson.links.map((link) => ({ title: toLf(link.title).trim(), url: link.url.trim() })),
|
|
21088
|
+
room_id: lesson.room_id,
|
|
21089
|
+
requires_paid_plan: lesson.requires_paid_plan,
|
|
21090
|
+
published: lesson.published
|
|
21091
|
+
};
|
|
21092
|
+
}
|
|
20848
21093
|
function rev(parts) {
|
|
20849
21094
|
return `${REV_VERSION}:${createHash3("sha256").update(JSON.stringify(parts)).digest("hex").slice(0, 12)}`;
|
|
20850
21095
|
}
|
|
@@ -20856,15 +21101,31 @@ function serverRoom(room) {
|
|
|
20856
21101
|
const content = roomContent(room);
|
|
20857
21102
|
return { content, rev: roomRev(room.room_id, content) };
|
|
20858
21103
|
}
|
|
21104
|
+
function serverCourse(course) {
|
|
21105
|
+
const content = courseContent(course);
|
|
21106
|
+
return { content, rev: courseRev(course.course_id, content) };
|
|
21107
|
+
}
|
|
21108
|
+
function serverModule(module) {
|
|
21109
|
+
const content = moduleContent(module);
|
|
21110
|
+
return { content, rev: moduleRev(module.module_id, content) };
|
|
21111
|
+
}
|
|
21112
|
+
function serverLesson(lesson) {
|
|
21113
|
+
const content = lessonContent(lesson);
|
|
21114
|
+
return { content, rev: lessonRev(lesson.lesson_id, content) };
|
|
21115
|
+
}
|
|
21116
|
+
function knownBase(base) {
|
|
21117
|
+
return base !== void 0 && base.startsWith(`${REV_VERSION}:`) ? base : void 0;
|
|
21118
|
+
}
|
|
20859
21119
|
function classify(base, file, server) {
|
|
20860
|
-
const known = base
|
|
21120
|
+
const known = knownBase(base);
|
|
20861
21121
|
if (known === void 0) return file === server ? "converged" : "conflict";
|
|
20862
|
-
|
|
20863
|
-
|
|
21122
|
+
return threeWay(file !== known, server !== known, file === server);
|
|
21123
|
+
}
|
|
21124
|
+
function threeWay(fileChanged, serverChanged, agree) {
|
|
20864
21125
|
if (!fileChanged && !serverChanged) return "in_sync";
|
|
20865
|
-
if (
|
|
21126
|
+
if (!serverChanged) return "local";
|
|
20866
21127
|
if (!fileChanged) return "remote";
|
|
20867
|
-
return
|
|
21128
|
+
return agree ? "converged" : "conflict";
|
|
20868
21129
|
}
|
|
20869
21130
|
function createClientId(communityId, kind, localName2) {
|
|
20870
21131
|
return uuidV5(communityId, `${kind}:${localName2}`);
|
|
@@ -20875,7 +21136,7 @@ function localName(name, id, taken, fallback) {
|
|
|
20875
21136
|
const withPrefix = `${slug}--${id.slice(0, 8)}`;
|
|
20876
21137
|
return taken.has(withPrefix) ? `${slug}--${id}` : withPrefix;
|
|
20877
21138
|
}
|
|
20878
|
-
var REV_VERSION, settingsRev, joinLinkRev, roomRev;
|
|
21139
|
+
var REV_VERSION, settingsRev, joinLinkRev, roomRev, courseRev, moduleRev, lessonRev;
|
|
20879
21140
|
var init_model = __esm({
|
|
20880
21141
|
"src/communities/model.ts"() {
|
|
20881
21142
|
"use strict";
|
|
@@ -20884,10 +21145,25 @@ var init_model = __esm({
|
|
|
20884
21145
|
settingsRev = (communityId, c) => rev(["settings", communityId, c.rules, c.slow_mode_seconds, c.courses_label]);
|
|
20885
21146
|
joinLinkRev = (communityId, c) => rev(["join_link", communityId, c.enabled]);
|
|
20886
21147
|
roomRev = (roomId, c) => rev(["room", roomId, c.name, c.archived]);
|
|
21148
|
+
courseRev = (courseId, c) => rev(["course", courseId, c.title, c.description, c.published]);
|
|
21149
|
+
moduleRev = (moduleId, c) => rev(["module", moduleId, c.title]);
|
|
21150
|
+
lessonRev = (lessonId, c) => rev([
|
|
21151
|
+
"lesson",
|
|
21152
|
+
lessonId,
|
|
21153
|
+
c.title,
|
|
21154
|
+
c.body,
|
|
21155
|
+
c.video?.provider ?? null,
|
|
21156
|
+
c.video?.id ?? null,
|
|
21157
|
+
c.links.map((link) => [link.title, link.url]),
|
|
21158
|
+
c.room_id,
|
|
21159
|
+
c.requires_paid_plan,
|
|
21160
|
+
c.published
|
|
21161
|
+
]);
|
|
20887
21162
|
}
|
|
20888
21163
|
});
|
|
20889
21164
|
|
|
20890
|
-
// src/communities/files.ts
|
|
21165
|
+
// src/communities/course-files.ts
|
|
21166
|
+
import * as fs18 from "fs";
|
|
20891
21167
|
import * as path24 from "path";
|
|
20892
21168
|
import * as yaml12 from "js-yaml";
|
|
20893
21169
|
function isMapping(value) {
|
|
@@ -20904,21 +21180,359 @@ function optionalString(doc, key, where, problems2) {
|
|
|
20904
21180
|
if (typeof value !== "string") problems2.push(`${where}: ${key} must be text`);
|
|
20905
21181
|
return typeof value === "string" ? value : void 0;
|
|
20906
21182
|
}
|
|
21183
|
+
function optionalBoolean(doc, key, where, problems2) {
|
|
21184
|
+
const value = doc[key];
|
|
21185
|
+
if (value === void 0 || value === null) return false;
|
|
21186
|
+
if (typeof value !== "boolean") problems2.push(`${where}: ${key} must be true or false`);
|
|
21187
|
+
return value === true;
|
|
21188
|
+
}
|
|
21189
|
+
function identity(doc, what, where, problems2) {
|
|
21190
|
+
const id = optionalString(doc, "id", where, problems2);
|
|
21191
|
+
if (id !== void 0 && !UUID_RE3.test(id)) problems2.push(`${where}: id must be ${what}'s id (a UUID)`);
|
|
21192
|
+
const rev2 = optionalString(doc, "rev", where, problems2);
|
|
21193
|
+
if (id === void 0 && rev2 !== void 0) problems2.push(`${where}: a rev without an id \u2014 remove the rev to create the ${what}`);
|
|
21194
|
+
return { ...id === void 0 ? {} : { id }, ...rev2 === void 0 ? {} : { rev: rev2 } };
|
|
21195
|
+
}
|
|
20907
21196
|
function contractProblem(error) {
|
|
20908
21197
|
const [issue] = error.issues;
|
|
20909
21198
|
return issue.path.length > 0 ? `${issue.path.map(String).join(".")}: ${issue.message}` : issue.message;
|
|
20910
21199
|
}
|
|
21200
|
+
function readIdList(raw, where, problems2) {
|
|
21201
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
21202
|
+
if (!Array.isArray(raw) || raw.some((id) => typeof id !== "string" || !UUID_RE3.test(id)) || new Set(raw).size !== raw.length) {
|
|
21203
|
+
problems2.push(`${where} must be a list of ids, each once \u2014 leave it as pull and push write it`);
|
|
21204
|
+
return void 0;
|
|
21205
|
+
}
|
|
21206
|
+
return raw;
|
|
21207
|
+
}
|
|
21208
|
+
function readCourseSynced(raw, where, problems2) {
|
|
21209
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
21210
|
+
if (!isMapping(raw)) {
|
|
21211
|
+
problems2.push(`${where}: synced must be a mapping`);
|
|
21212
|
+
return void 0;
|
|
21213
|
+
}
|
|
21214
|
+
unknownKeys(raw, COURSE_SYNCED_KEYS, `${where} synced`, problems2);
|
|
21215
|
+
const lessons = {};
|
|
21216
|
+
if (raw.lessons !== void 0 && raw.lessons !== null) {
|
|
21217
|
+
if (!isMapping(raw.lessons)) problems2.push(`${where}: synced.lessons must be a mapping of module ids`);
|
|
21218
|
+
else {
|
|
21219
|
+
for (const [moduleId, ids] of Object.entries(raw.lessons)) {
|
|
21220
|
+
if (!UUID_RE3.test(moduleId)) problems2.push(`${where}: synced.lessons: ${JSON.stringify(moduleId)} is not a module id`);
|
|
21221
|
+
const list = readIdList(ids, `${where} synced.lessons.${moduleId}`, problems2);
|
|
21222
|
+
if (list) lessons[moduleId] = list;
|
|
21223
|
+
}
|
|
21224
|
+
}
|
|
21225
|
+
}
|
|
21226
|
+
const modules = readIdList(raw.modules, `${where} synced.modules`, problems2);
|
|
21227
|
+
return { ...modules === void 0 ? {} : { modules }, lessons };
|
|
21228
|
+
}
|
|
21229
|
+
function readNameList(raw, where, what, problems2) {
|
|
21230
|
+
if (raw === void 0 || raw === null) return [];
|
|
21231
|
+
if (!Array.isArray(raw)) {
|
|
21232
|
+
problems2.push(`${where} must be a list of ${what} names`);
|
|
21233
|
+
return [];
|
|
21234
|
+
}
|
|
21235
|
+
const names = [];
|
|
21236
|
+
for (const name of raw) {
|
|
21237
|
+
if (typeof name !== "string" || !isPathSafeId(name)) {
|
|
21238
|
+
problems2.push(`${where}: ${JSON.stringify(name)} is not a ${what} name \u2014 ${PATH_SAFE_ID_RULE}`);
|
|
21239
|
+
} else if (names.includes(name)) {
|
|
21240
|
+
problems2.push(`${where}: ${name} is listed twice`);
|
|
21241
|
+
} else {
|
|
21242
|
+
names.push(name);
|
|
21243
|
+
}
|
|
21244
|
+
}
|
|
21245
|
+
return names;
|
|
21246
|
+
}
|
|
21247
|
+
function readCourseFolders(communityFolder, slugs, problems2) {
|
|
21248
|
+
const coursesDir = path24.join(communityFolder, COURSES_DIR);
|
|
21249
|
+
requireRealSubdirNoSymlink(communityFolder, coursesDir, false);
|
|
21250
|
+
const courses = [];
|
|
21251
|
+
for (const slug of slugs) {
|
|
21252
|
+
const folder = path24.join(coursesDir, slug);
|
|
21253
|
+
requireRealSubdirNoSymlink(communityFolder, folder, false);
|
|
21254
|
+
if (!isRealDirectory(folder)) {
|
|
21255
|
+
problems2.push(`${COMMUNITY_FILE} courses: ${slug} has no folder ${COURSES_DIR}/${slug}/`);
|
|
21256
|
+
continue;
|
|
21257
|
+
}
|
|
21258
|
+
const course = readCourseFolder(communityFolder, folder, slug, problems2);
|
|
21259
|
+
if (course) courses.push(course);
|
|
21260
|
+
}
|
|
21261
|
+
const unlisted = folderEntries(coursesDir).filter((name) => !slugs.includes(name) && isRealDirectory(path24.join(coursesDir, name))).map((name) => ({ name, id: readYamlIdNoFollow(communityFolder, path24.join(coursesDir, name, COURSE_FILE), "id") }));
|
|
21262
|
+
return { courses, unlisted };
|
|
21263
|
+
}
|
|
21264
|
+
function readCourseFolder(communityFolder, folder, slug, problems2) {
|
|
21265
|
+
const where = `${COURSES_DIR}/${slug}/${COURSE_FILE}`;
|
|
21266
|
+
const text = readRealFileOrThrow(communityFolder, path24.join(folder, COURSE_FILE));
|
|
21267
|
+
if (text === null) {
|
|
21268
|
+
problems2.push(`${COURSES_DIR}/${slug}/: no ${COURSE_FILE}`);
|
|
21269
|
+
return null;
|
|
21270
|
+
}
|
|
21271
|
+
let doc;
|
|
21272
|
+
try {
|
|
21273
|
+
doc = yaml12.load(text);
|
|
21274
|
+
} catch (err) {
|
|
21275
|
+
problems2.push(`${where} is not valid YAML: ${err instanceof Error ? err.message : String(err)}`);
|
|
21276
|
+
return null;
|
|
21277
|
+
}
|
|
21278
|
+
if (!isMapping(doc)) {
|
|
21279
|
+
problems2.push(`${where} must be a YAML mapping`);
|
|
21280
|
+
return null;
|
|
21281
|
+
}
|
|
21282
|
+
unknownKeys(doc, COURSE_KEYS, where, problems2);
|
|
21283
|
+
const { id, rev: rev2 } = identity(doc, "a course", where, problems2);
|
|
21284
|
+
const synced2 = readCourseSynced(doc.synced, where, problems2);
|
|
21285
|
+
if (id === void 0 && synced2 !== void 0) problems2.push(`${where}: synced without an id \u2014 remove it to create the course`);
|
|
21286
|
+
const description2 = doc.description ?? "";
|
|
21287
|
+
const parsed = communityCourseCreateBody.safeParse({ title: doc.title, description: description2 });
|
|
21288
|
+
if (!parsed.success) problems2.push(`${where}: ${contractProblem(parsed.error)}`);
|
|
21289
|
+
const published = optionalBoolean(doc, "published", where, problems2);
|
|
21290
|
+
const lessonsDir = path24.join(folder, LESSONS_DIR);
|
|
21291
|
+
requireRealSubdirNoSymlink(communityFolder, lessonsDir, false);
|
|
21292
|
+
const modules = readModules(communityFolder, lessonsDir, doc.modules, slug, where, problems2);
|
|
21293
|
+
const listed2 = new Set(modules.flatMap((module) => module.lessons.map((lesson) => lesson.slug)));
|
|
21294
|
+
const unlisted = folderEntries(lessonsDir).filter((name) => name.endsWith(LESSON_FILE_EXT) && !listed2.has(path24.basename(name, LESSON_FILE_EXT))).map((name) => ({ name, id: uuidField(lessonFrontmatter(readFileNoFollow(communityFolder, path24.join(lessonsDir, name))), "id") }));
|
|
21295
|
+
if (!parsed.success) return null;
|
|
21296
|
+
return {
|
|
21297
|
+
slug,
|
|
21298
|
+
...id === void 0 ? {} : { id },
|
|
21299
|
+
...rev2 === void 0 ? {} : { rev: rev2 },
|
|
21300
|
+
title: doc.title,
|
|
21301
|
+
description: description2,
|
|
21302
|
+
published,
|
|
21303
|
+
modules,
|
|
21304
|
+
...synced2 === void 0 ? {} : { synced: synced2 },
|
|
21305
|
+
unlisted
|
|
21306
|
+
};
|
|
21307
|
+
}
|
|
21308
|
+
function readModules(communityFolder, lessonsDir, raw, courseSlug, courseWhere, problems2) {
|
|
21309
|
+
if (raw === void 0 || raw === null) return [];
|
|
21310
|
+
if (!Array.isArray(raw)) {
|
|
21311
|
+
problems2.push(`${courseWhere}: modules must be a list`);
|
|
21312
|
+
return [];
|
|
21313
|
+
}
|
|
21314
|
+
const modules = [];
|
|
21315
|
+
const keys = /* @__PURE__ */ new Set();
|
|
21316
|
+
const listedLessons = /* @__PURE__ */ new Set();
|
|
21317
|
+
raw.forEach((entry, index) => {
|
|
21318
|
+
const at = `${courseWhere} modules[${index}]`;
|
|
21319
|
+
if (!isMapping(entry)) {
|
|
21320
|
+
problems2.push(`${at}: must be a mapping`);
|
|
21321
|
+
return;
|
|
21322
|
+
}
|
|
21323
|
+
unknownKeys(entry, MODULE_KEYS, at, problems2);
|
|
21324
|
+
const key = entry.key;
|
|
21325
|
+
if (typeof key !== "string" || !isPathSafeId(key)) {
|
|
21326
|
+
problems2.push(`${at}: key must be a plain name \u2014 ${PATH_SAFE_ID_RULE}`);
|
|
21327
|
+
return;
|
|
21328
|
+
}
|
|
21329
|
+
const where = `${courseWhere} module ${key}`;
|
|
21330
|
+
if (keys.has(key)) problems2.push(`${where}: the key is used twice`);
|
|
21331
|
+
keys.add(key);
|
|
21332
|
+
const { id, rev: rev2 } = identity(entry, "a module", where, problems2);
|
|
21333
|
+
const parsed = communityModuleCreateBody.safeParse({ title: entry.title });
|
|
21334
|
+
if (!parsed.success) problems2.push(`${where}: ${contractProblem(parsed.error)}`);
|
|
21335
|
+
const lessons = [];
|
|
21336
|
+
for (const slug of readNameList(entry.lessons, `${where} lessons`, "lesson", problems2)) {
|
|
21337
|
+
if (listedLessons.has(slug)) {
|
|
21338
|
+
problems2.push(`${courseWhere}: lesson ${slug} is listed twice`);
|
|
21339
|
+
continue;
|
|
21340
|
+
}
|
|
21341
|
+
listedLessons.add(slug);
|
|
21342
|
+
const lesson = readLessonFile(communityFolder, lessonsDir, courseSlug, slug, problems2);
|
|
21343
|
+
if (lesson) lessons.push(lesson);
|
|
21344
|
+
}
|
|
21345
|
+
if (!parsed.success) return;
|
|
21346
|
+
modules.push({
|
|
21347
|
+
key,
|
|
21348
|
+
...id === void 0 ? {} : { id },
|
|
21349
|
+
...rev2 === void 0 ? {} : { rev: rev2 },
|
|
21350
|
+
title: entry.title,
|
|
21351
|
+
lessons
|
|
21352
|
+
});
|
|
21353
|
+
});
|
|
21354
|
+
return modules;
|
|
21355
|
+
}
|
|
21356
|
+
function splitFrontmatter(text) {
|
|
21357
|
+
if (!text.startsWith("---\n")) return null;
|
|
21358
|
+
const close2 = text.indexOf("\n---\n", 3);
|
|
21359
|
+
if (close2 === -1) return text.endsWith("\n---") ? { front: text.slice(4, -3), body: "" } : null;
|
|
21360
|
+
const rest = text.slice(close2 + 5);
|
|
21361
|
+
return { front: text.slice(4, close2 + 1), body: rest.endsWith("\n") ? rest.slice(0, -1) : rest };
|
|
21362
|
+
}
|
|
21363
|
+
function readLessonFile(communityFolder, lessonsDir, courseSlug, slug, problems2) {
|
|
21364
|
+
const name = `${slug}${LESSON_FILE_EXT}`;
|
|
21365
|
+
const where = `${COURSES_DIR}/${courseSlug}/${LESSONS_DIR}/${name}`;
|
|
21366
|
+
const raw = readRealFileOrThrow(communityFolder, path24.join(lessonsDir, name));
|
|
21367
|
+
if (raw === null) {
|
|
21368
|
+
const hint = slug.endsWith(LESSON_FILE_EXT) ? ` (list a lesson by its file's name less ${LESSON_FILE_EXT})` : "";
|
|
21369
|
+
problems2.push(`${COURSES_DIR}/${courseSlug}/${COURSE_FILE}: lesson ${slug} has no file ${LESSONS_DIR}/${name}${hint}`);
|
|
21370
|
+
return null;
|
|
21371
|
+
}
|
|
21372
|
+
const parts = splitFrontmatter(toLf(raw));
|
|
21373
|
+
if (!parts) {
|
|
21374
|
+
problems2.push(`${where}: must start with a frontmatter block (a line --- , the lesson's fields, a line ---)`);
|
|
21375
|
+
return null;
|
|
21376
|
+
}
|
|
21377
|
+
let doc;
|
|
21378
|
+
try {
|
|
21379
|
+
doc = parts.front.trim() === "" ? {} : yaml12.load(parts.front);
|
|
21380
|
+
} catch (err) {
|
|
21381
|
+
problems2.push(`${where}: the frontmatter is not valid YAML: ${err instanceof Error ? err.message : String(err)}`);
|
|
21382
|
+
return null;
|
|
21383
|
+
}
|
|
21384
|
+
if (!isMapping(doc)) {
|
|
21385
|
+
problems2.push(`${where}: the frontmatter must be a YAML mapping`);
|
|
21386
|
+
return null;
|
|
21387
|
+
}
|
|
21388
|
+
unknownKeys(doc, LESSON_KEYS, where, problems2);
|
|
21389
|
+
const { id, rev: rev2 } = identity(doc, "a lesson", where, problems2);
|
|
21390
|
+
const room = optionalString(doc, "room", where, problems2);
|
|
21391
|
+
if (room !== void 0 && !isPathSafeId(room)) problems2.push(`${where}: room must be a room's key in ${COMMUNITY_FILE}`);
|
|
21392
|
+
const videoUrl = optionalString(doc, "video_url", where, problems2);
|
|
21393
|
+
const published = optionalBoolean(doc, "published", where, problems2);
|
|
21394
|
+
const requiresPaidPlan = optionalBoolean(doc, "requires_paid_plan", where, problems2);
|
|
21395
|
+
const links = doc.links ?? [];
|
|
21396
|
+
const parsed = lessonFileBody.safeParse({
|
|
21397
|
+
title: doc.title,
|
|
21398
|
+
...videoUrl === void 0 ? {} : { video_url: videoUrl },
|
|
21399
|
+
body: parts.body,
|
|
21400
|
+
links
|
|
21401
|
+
});
|
|
21402
|
+
if (!parsed.success) {
|
|
21403
|
+
problems2.push(`${where}: ${contractProblem(parsed.error)}`);
|
|
21404
|
+
return null;
|
|
21405
|
+
}
|
|
21406
|
+
return {
|
|
21407
|
+
slug,
|
|
21408
|
+
...id === void 0 ? {} : { id },
|
|
21409
|
+
...rev2 === void 0 ? {} : { rev: rev2 },
|
|
21410
|
+
title: doc.title,
|
|
21411
|
+
published,
|
|
21412
|
+
...room === void 0 ? {} : { room },
|
|
21413
|
+
...videoUrl === void 0 ? {} : { video_url: videoUrl },
|
|
21414
|
+
links: links.map((link) => ({ title: link.title, url: link.url })),
|
|
21415
|
+
requires_paid_plan: requiresPaidPlan,
|
|
21416
|
+
body: parts.body
|
|
21417
|
+
};
|
|
21418
|
+
}
|
|
21419
|
+
function folderEntries(dir) {
|
|
21420
|
+
try {
|
|
21421
|
+
return fs18.readdirSync(dir).filter((name) => !name.startsWith(".")).sort();
|
|
21422
|
+
} catch {
|
|
21423
|
+
return [];
|
|
21424
|
+
}
|
|
21425
|
+
}
|
|
21426
|
+
function lessonFrontmatter(bytes) {
|
|
21427
|
+
const parts = bytes ? splitFrontmatter(toLf(bytes.toString("utf-8"))) : null;
|
|
21428
|
+
if (!parts) return null;
|
|
21429
|
+
try {
|
|
21430
|
+
return yaml12.load(parts.front);
|
|
21431
|
+
} catch {
|
|
21432
|
+
return null;
|
|
21433
|
+
}
|
|
21434
|
+
}
|
|
21435
|
+
function renderCourseYaml(course) {
|
|
21436
|
+
const doc = {
|
|
21437
|
+
...course.id ? { id: course.id } : {},
|
|
21438
|
+
...course.rev ? { rev: course.rev } : {},
|
|
21439
|
+
title: course.title,
|
|
21440
|
+
description: course.description,
|
|
21441
|
+
published: course.published,
|
|
21442
|
+
modules: course.modules.map((module) => ({
|
|
21443
|
+
key: module.key,
|
|
21444
|
+
...module.id ? { id: module.id } : {},
|
|
21445
|
+
...module.rev ? { rev: module.rev } : {},
|
|
21446
|
+
title: module.title,
|
|
21447
|
+
lessons: module.lessons.map((lesson) => lesson.slug)
|
|
21448
|
+
})),
|
|
21449
|
+
...course.synced ? {
|
|
21450
|
+
synced: {
|
|
21451
|
+
...course.synced.modules ? { modules: course.synced.modules } : {},
|
|
21452
|
+
lessons: Object.fromEntries(
|
|
21453
|
+
Object.entries(course.synced.lessons).sort(([a], [b]) => syncedRank(course, a) - syncedRank(course, b) || a.localeCompare(b))
|
|
21454
|
+
)
|
|
21455
|
+
}
|
|
21456
|
+
} : {}
|
|
21457
|
+
};
|
|
21458
|
+
return yaml12.dump(doc, YAML_DUMP_OPTIONS);
|
|
21459
|
+
}
|
|
21460
|
+
function syncedRank(course, moduleId) {
|
|
21461
|
+
const at = course.synced?.modules?.indexOf(moduleId) ?? -1;
|
|
21462
|
+
return at === -1 ? Number.MAX_SAFE_INTEGER : at;
|
|
21463
|
+
}
|
|
21464
|
+
function renderLessonFile(lesson) {
|
|
21465
|
+
const front = {
|
|
21466
|
+
...lesson.id ? { id: lesson.id } : {},
|
|
21467
|
+
...lesson.rev ? { rev: lesson.rev } : {},
|
|
21468
|
+
title: lesson.title,
|
|
21469
|
+
published: lesson.published,
|
|
21470
|
+
...lesson.room ? { room: lesson.room } : {},
|
|
21471
|
+
...lesson.video_url ? { video_url: lesson.video_url } : {},
|
|
21472
|
+
...lesson.links.length > 0 ? { links: lesson.links.map((link) => ({ title: link.title, url: link.url })) } : {},
|
|
21473
|
+
requires_paid_plan: lesson.requires_paid_plan
|
|
21474
|
+
};
|
|
21475
|
+
return `---
|
|
21476
|
+
${yaml12.dump(front, YAML_DUMP_OPTIONS)}---
|
|
21477
|
+
${toLf(lesson.body)}
|
|
21478
|
+
`;
|
|
21479
|
+
}
|
|
21480
|
+
function writeCourseYaml(root, communityFolder, course) {
|
|
21481
|
+
return writeFileIfChangedNoFollow(root, path24.join(communityFolder, coursePath(course)), renderCourseYaml(course));
|
|
21482
|
+
}
|
|
21483
|
+
function writeLessonFile(root, communityFolder, course, lesson) {
|
|
21484
|
+
return writeFileIfChangedNoFollow(root, path24.join(communityFolder, lessonPath(course, lesson)), renderLessonFile(lesson));
|
|
21485
|
+
}
|
|
21486
|
+
function writeCourseFolder(root, communityFolder, course) {
|
|
21487
|
+
const written = writeCourseYaml(root, communityFolder, course) ? [coursePath(course)] : [];
|
|
21488
|
+
for (const module of course.modules) {
|
|
21489
|
+
for (const lesson of module.lessons) {
|
|
21490
|
+
if (writeLessonFile(root, communityFolder, course, lesson)) written.push(lessonPath(course, lesson));
|
|
21491
|
+
}
|
|
21492
|
+
}
|
|
21493
|
+
return written;
|
|
21494
|
+
}
|
|
21495
|
+
var COURSES_DIR, COURSE_FILE, LESSONS_DIR, LESSON_FILE_EXT, COURSE_KEYS, MODULE_KEYS, COURSE_SYNCED_KEYS, LESSON_KEYS, lessonFileBody, coursePath, lessonPath, unlistedPath;
|
|
21496
|
+
var init_course_files = __esm({
|
|
21497
|
+
"src/communities/course-files.ts"() {
|
|
21498
|
+
"use strict";
|
|
21499
|
+
init_contracts();
|
|
21500
|
+
init_base_id();
|
|
21501
|
+
init_community_workspace();
|
|
21502
|
+
init_fs_safety();
|
|
21503
|
+
init_layout();
|
|
21504
|
+
init_yaml_writer();
|
|
21505
|
+
init_utils();
|
|
21506
|
+
init_model();
|
|
21507
|
+
COURSES_DIR = "courses";
|
|
21508
|
+
COURSE_FILE = "course.yaml";
|
|
21509
|
+
LESSONS_DIR = "lessons";
|
|
21510
|
+
LESSON_FILE_EXT = ".md";
|
|
21511
|
+
COURSE_KEYS = ["id", "rev", "title", "description", "published", "modules", "synced"];
|
|
21512
|
+
MODULE_KEYS = ["key", "id", "rev", "title", "lessons"];
|
|
21513
|
+
COURSE_SYNCED_KEYS = ["modules", "lessons"];
|
|
21514
|
+
LESSON_KEYS = ["id", "rev", "title", "published", "room", "video_url", "links", "requires_paid_plan"];
|
|
21515
|
+
lessonFileBody = communityLessonCreateBody.omit({ module_id: true, room_id: true, client_id: true });
|
|
21516
|
+
coursePath = (course) => path24.join(COURSES_DIR, course.slug, COURSE_FILE);
|
|
21517
|
+
lessonPath = (course, lesson) => path24.join(COURSES_DIR, course.slug, LESSONS_DIR, `${lesson.slug}${LESSON_FILE_EXT}`);
|
|
21518
|
+
unlistedPath = (name, course) => course ? path24.join(COURSES_DIR, course.slug, LESSONS_DIR, name) : `${path24.join(COURSES_DIR, name)}/`;
|
|
21519
|
+
}
|
|
21520
|
+
});
|
|
21521
|
+
|
|
21522
|
+
// src/communities/files.ts
|
|
21523
|
+
import * as path25 from "path";
|
|
21524
|
+
import * as yaml13 from "js-yaml";
|
|
20911
21525
|
function readCommunityFolder(folder) {
|
|
20912
|
-
const where =
|
|
20913
|
-
const yamlPath =
|
|
21526
|
+
const where = path25.basename(folder);
|
|
21527
|
+
const yamlPath = path25.join(folder, COMMUNITY_FILE);
|
|
20914
21528
|
const text = readRealFileOrThrow(folder, yamlPath);
|
|
20915
21529
|
if (text === null) {
|
|
20916
21530
|
throw expected(`No ${COMMUNITY_FILE} in ${where}/: pull the community first (wayai pull communities/<community_id>).`);
|
|
20917
21531
|
}
|
|
20918
|
-
const rules = toLf(readRealFileOrThrow(folder,
|
|
21532
|
+
const rules = toLf(readRealFileOrThrow(folder, path25.join(folder, RULES_FILE)) ?? "");
|
|
20919
21533
|
let doc;
|
|
20920
21534
|
try {
|
|
20921
|
-
doc =
|
|
21535
|
+
doc = yaml13.load(text);
|
|
20922
21536
|
} catch (err) {
|
|
20923
21537
|
throw expected(`${where}/${COMMUNITY_FILE} is not valid YAML: ${err instanceof Error ? err.message : String(err)}`);
|
|
20924
21538
|
}
|
|
@@ -20945,6 +21559,9 @@ function readCommunityFolder(folder) {
|
|
|
20945
21559
|
if (typeof doc.join_link_enabled !== "boolean") problems2.push(`${COMMUNITY_FILE}: join_link_enabled must be true or false`);
|
|
20946
21560
|
const joinLinkRev2 = optionalString(doc, "join_link_rev", COMMUNITY_FILE, problems2);
|
|
20947
21561
|
const rooms = readRooms(doc.rooms, problems2);
|
|
21562
|
+
const synced2 = readCommunitySynced(doc.synced, problems2);
|
|
21563
|
+
const { courses, unlisted } = readCourseFolders(folder, readNameList(doc.courses, `${COMMUNITY_FILE} courses`, "course folder", problems2), problems2);
|
|
21564
|
+
problems2.push(...crossFileProblems(rooms, courses));
|
|
20948
21565
|
if (problems2.length > 0) {
|
|
20949
21566
|
throw expected(`${where}/ cannot be synced:
|
|
20950
21567
|
${problems2.map((p) => ` - ${p}`).join("\n")}`);
|
|
@@ -20960,8 +21577,44 @@ ${problems2.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
20960
21577
|
rules,
|
|
20961
21578
|
join_link_enabled: doc.join_link_enabled,
|
|
20962
21579
|
...joinLinkRev2 === void 0 ? {} : { join_link_rev: joinLinkRev2 },
|
|
20963
|
-
rooms
|
|
21580
|
+
rooms,
|
|
21581
|
+
courses,
|
|
21582
|
+
...synced2 === void 0 ? {} : { synced_courses: synced2 },
|
|
21583
|
+
unlistedCourses: unlisted
|
|
21584
|
+
};
|
|
21585
|
+
}
|
|
21586
|
+
function readCommunitySynced(raw, problems2) {
|
|
21587
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
21588
|
+
if (!isMapping(raw)) {
|
|
21589
|
+
problems2.push(`${COMMUNITY_FILE}: synced must be a mapping`);
|
|
21590
|
+
return void 0;
|
|
21591
|
+
}
|
|
21592
|
+
unknownKeys(raw, ["courses"], `${COMMUNITY_FILE} synced`, problems2);
|
|
21593
|
+
return readIdList(raw.courses, `${COMMUNITY_FILE} synced.courses`, problems2);
|
|
21594
|
+
}
|
|
21595
|
+
function crossFileProblems(rooms, courses) {
|
|
21596
|
+
const problems2 = [];
|
|
21597
|
+
const seen = /* @__PURE__ */ new Map();
|
|
21598
|
+
const once = (id, where) => {
|
|
21599
|
+
if (id === void 0) return;
|
|
21600
|
+
const first = seen.get(id);
|
|
21601
|
+
if (first) problems2.push(`${where}: id ${id} is also ${first}'s \u2014 a copy keeps its original's id; remove the id (and its revs) to make a new one`);
|
|
21602
|
+
else seen.set(id, where);
|
|
20964
21603
|
};
|
|
21604
|
+
const roomKeys = new Set(rooms.map((room) => room.key));
|
|
21605
|
+
for (const course of courses) {
|
|
21606
|
+
once(course.id, `course ${course.slug}`);
|
|
21607
|
+
for (const module of course.modules) {
|
|
21608
|
+
once(module.id, `module ${course.slug}/${module.key}`);
|
|
21609
|
+
for (const lesson of module.lessons) {
|
|
21610
|
+
once(lesson.id, `lesson ${course.slug}/${lesson.slug}`);
|
|
21611
|
+
if (lesson.room !== void 0 && !roomKeys.has(lesson.room)) {
|
|
21612
|
+
problems2.push(`lesson ${course.slug}/${lesson.slug}: room ${lesson.room} is not a room key in ${COMMUNITY_FILE}`);
|
|
21613
|
+
}
|
|
21614
|
+
}
|
|
21615
|
+
}
|
|
21616
|
+
}
|
|
21617
|
+
return problems2;
|
|
20965
21618
|
}
|
|
20966
21619
|
function readRooms(raw, problems2) {
|
|
20967
21620
|
if (raw === void 0 || raw === null) return [];
|
|
@@ -21033,31 +21686,30 @@ function renderCommunityYaml(files) {
|
|
|
21033
21686
|
name: room.name,
|
|
21034
21687
|
kind: room.kind,
|
|
21035
21688
|
...room.archived ? { archived: true } : {}
|
|
21036
|
-
}))
|
|
21689
|
+
})),
|
|
21690
|
+
courses: files.courses.map((course) => course.slug),
|
|
21691
|
+
...files.synced_courses ? { synced: { courses: files.synced_courses } } : {}
|
|
21037
21692
|
};
|
|
21038
|
-
return
|
|
21693
|
+
return yaml13.dump(doc, YAML_DUMP_OPTIONS);
|
|
21039
21694
|
}
|
|
21040
21695
|
function renderRules(rules) {
|
|
21041
21696
|
const text = toLf(rules).replace(/\n+$/, "");
|
|
21042
21697
|
return text === "" ? "" : `${text}
|
|
21043
21698
|
`;
|
|
21044
21699
|
}
|
|
21045
|
-
function
|
|
21700
|
+
function writeCommunityFiles(root, folder, files) {
|
|
21046
21701
|
const written = [];
|
|
21047
21702
|
for (const [name, content] of [
|
|
21048
21703
|
[COMMUNITY_FILE, renderCommunityYaml(files)],
|
|
21049
21704
|
[RULES_FILE, renderRules(files.rules)]
|
|
21050
21705
|
]) {
|
|
21051
|
-
|
|
21052
|
-
const bytes = Buffer.from(content, "utf-8");
|
|
21053
|
-
if (fileHasBytes(abs, bytes)) continue;
|
|
21054
|
-
if (!writeFileNoFollow(root, abs, bytes)) {
|
|
21055
|
-
throw symlinkRefusal(path24.relative(path24.dirname(root), abs), "file");
|
|
21056
|
-
}
|
|
21057
|
-
written.push(name);
|
|
21706
|
+
if (writeFileIfChangedNoFollow(root, path25.join(folder, name), content)) written.push(name);
|
|
21058
21707
|
}
|
|
21059
21708
|
return written;
|
|
21060
21709
|
}
|
|
21710
|
+
function writeCommunityFolder(root, folder, files) {
|
|
21711
|
+
return [...writeCommunityFiles(root, folder, files), ...files.courses.flatMap((course) => writeCourseFolder(root, folder, course))];
|
|
21712
|
+
}
|
|
21061
21713
|
var TOP_KEYS, SETTINGS_KEYS, ROOM_KEYS;
|
|
21062
21714
|
var init_files = __esm({
|
|
21063
21715
|
"src/communities/files.ts"() {
|
|
@@ -21070,20 +21722,1365 @@ var init_files = __esm({
|
|
|
21070
21722
|
init_community_workspace();
|
|
21071
21723
|
init_utils();
|
|
21072
21724
|
init_model();
|
|
21073
|
-
|
|
21725
|
+
init_course_files();
|
|
21726
|
+
TOP_KEYS = ["community_id", "name", "settings", "join_link_enabled", "join_link_rev", "rooms", "courses", "synced"];
|
|
21074
21727
|
SETTINGS_KEYS = ["rev", "slow_mode_seconds", "courses_label"];
|
|
21075
21728
|
ROOM_KEYS = ["key", "id", "rev", "name", "kind", "archived"];
|
|
21076
21729
|
}
|
|
21077
21730
|
});
|
|
21078
21731
|
|
|
21732
|
+
// src/communities/listing.ts
|
|
21733
|
+
function lineDiff(from, to) {
|
|
21734
|
+
const a = from ? sanitizeTerminalText(from).split("\n") : [];
|
|
21735
|
+
const b = to ? sanitizeTerminalText(to).split("\n") : [];
|
|
21736
|
+
if (a.length * b.length > 25e4) return [...a.map((l) => `- ${l}`), ...b.map((l) => `+ ${l}`)];
|
|
21737
|
+
const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
21738
|
+
for (let i2 = a.length - 1; i2 >= 0; i2--) {
|
|
21739
|
+
for (let j2 = b.length - 1; j2 >= 0; j2--) {
|
|
21740
|
+
lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
|
|
21741
|
+
}
|
|
21742
|
+
}
|
|
21743
|
+
const out = [];
|
|
21744
|
+
let i = 0;
|
|
21745
|
+
let j = 0;
|
|
21746
|
+
while (i < a.length || j < b.length) {
|
|
21747
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
21748
|
+
i++;
|
|
21749
|
+
j++;
|
|
21750
|
+
} else if (j >= b.length || i < a.length && lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
21751
|
+
out.push(`- ${a[i++]}`);
|
|
21752
|
+
} else {
|
|
21753
|
+
out.push(`+ ${b[j++]}`);
|
|
21754
|
+
}
|
|
21755
|
+
}
|
|
21756
|
+
return out;
|
|
21757
|
+
}
|
|
21758
|
+
function printSection(title, lines) {
|
|
21759
|
+
if (lines.length === 0) return;
|
|
21760
|
+
console.log(`
|
|
21761
|
+
${title}`);
|
|
21762
|
+
for (const line of lines) console.log(` ${line}`);
|
|
21763
|
+
}
|
|
21764
|
+
function printListing(listing) {
|
|
21765
|
+
printSection("Changes:", listing.changes);
|
|
21766
|
+
printSection("Changed on the server since your last pull \u2014 left as is; pull to take it:", listing.serverChanged);
|
|
21767
|
+
printSection("Kept \u2014 edited in your files, not pushed yet:", listing.kept);
|
|
21768
|
+
printSection("Not in your files \u2014 left as is:", listing.leftAsIs);
|
|
21769
|
+
printSection("Conflicts \u2014 changed in your files and on the server since your last pull:", listing.conflicts);
|
|
21770
|
+
}
|
|
21771
|
+
var show, emptyListing;
|
|
21772
|
+
var init_listing = __esm({
|
|
21773
|
+
"src/communities/listing.ts"() {
|
|
21774
|
+
"use strict";
|
|
21775
|
+
init_terminal_output();
|
|
21776
|
+
show = (text) => JSON.stringify(sanitizeTerminalText(text));
|
|
21777
|
+
emptyListing = () => ({ changes: [], serverChanged: [], conflicts: [], leftAsIs: [], kept: [] });
|
|
21778
|
+
}
|
|
21779
|
+
});
|
|
21780
|
+
|
|
21781
|
+
// src/communities/order.ts
|
|
21782
|
+
function idsOf(entries) {
|
|
21783
|
+
return new Set(entries.flatMap((entry) => entry.id === void 0 ? [] : [entry.id]));
|
|
21784
|
+
}
|
|
21785
|
+
function restrict(order, ids) {
|
|
21786
|
+
const keep = new Set(ids);
|
|
21787
|
+
return order.filter((id) => keep.has(id));
|
|
21788
|
+
}
|
|
21789
|
+
function mergeOrder(listed2, server, named) {
|
|
21790
|
+
const listedIds2 = idsOf(listed2);
|
|
21791
|
+
const first = [];
|
|
21792
|
+
const after = /* @__PURE__ */ new Map();
|
|
21793
|
+
let anchor = null;
|
|
21794
|
+
for (const id of server) {
|
|
21795
|
+
if (listedIds2.has(id)) {
|
|
21796
|
+
anchor = id;
|
|
21797
|
+
} else if (!named.has(id)) {
|
|
21798
|
+
if (anchor === null) first.push(id);
|
|
21799
|
+
else after.set(anchor, [...after.get(anchor) ?? [], id]);
|
|
21800
|
+
}
|
|
21801
|
+
}
|
|
21802
|
+
const slots = first.map((id) => ({ untracked: id }));
|
|
21803
|
+
for (const child of listed2) {
|
|
21804
|
+
slots.push({ entry: child.entry });
|
|
21805
|
+
if (child.id !== void 0) for (const id of after.get(child.id) ?? []) slots.push({ untracked: id });
|
|
21806
|
+
}
|
|
21807
|
+
return slots;
|
|
21808
|
+
}
|
|
21809
|
+
function filesOrder(listed2, server, named) {
|
|
21810
|
+
const byEntry = new Map(listed2.map((child) => [child.entry, child.id]));
|
|
21811
|
+
return mergeOrder(listed2, server, named).flatMap((slot) => {
|
|
21812
|
+
const id = "untracked" in slot ? slot.untracked : byEntry.get(slot.entry);
|
|
21813
|
+
return id === void 0 ? [] : [id];
|
|
21814
|
+
});
|
|
21815
|
+
}
|
|
21816
|
+
function placeAt(list, id, position) {
|
|
21817
|
+
const at = list.indexOf(id);
|
|
21818
|
+
if (at >= 0) list.splice(at, 1);
|
|
21819
|
+
list.splice(Math.min(position, list.length), 0, id);
|
|
21820
|
+
}
|
|
21821
|
+
function sameOrder(a, b) {
|
|
21822
|
+
return a.length === b.length && a.every((id, i) => id === b[i]);
|
|
21823
|
+
}
|
|
21824
|
+
function reorderMoves(current, target) {
|
|
21825
|
+
const list = [...current];
|
|
21826
|
+
const present = target.filter((id) => list.includes(id));
|
|
21827
|
+
const stay = longestRising(present, present.map((id) => list.indexOf(id)));
|
|
21828
|
+
const moves = [];
|
|
21829
|
+
target.forEach((id, i) => {
|
|
21830
|
+
if (stay.has(id)) return;
|
|
21831
|
+
const others = list.filter((other) => other !== id);
|
|
21832
|
+
const position = i === 0 ? 0 : others.indexOf(target[i - 1]) + 1;
|
|
21833
|
+
moves.push({ id, position });
|
|
21834
|
+
placeAt(list, id, position);
|
|
21835
|
+
});
|
|
21836
|
+
const placed = list.filter((id) => target.includes(id));
|
|
21837
|
+
if (!sameOrder(placed, target)) {
|
|
21838
|
+
throw new Error(`reorderMoves: ${JSON.stringify(current)} \u2192 ${JSON.stringify(target)} left ${JSON.stringify(placed)}`);
|
|
21839
|
+
}
|
|
21840
|
+
return moves;
|
|
21841
|
+
}
|
|
21842
|
+
function longestRising(ids, positions) {
|
|
21843
|
+
const length = positions.map(() => 1);
|
|
21844
|
+
const previous = positions.map(() => -1);
|
|
21845
|
+
let best = -1;
|
|
21846
|
+
positions.forEach((position, i) => {
|
|
21847
|
+
for (let j = 0; j < i; j++) {
|
|
21848
|
+
if (positions[j] < position && length[j] + 1 > length[i]) {
|
|
21849
|
+
length[i] = length[j] + 1;
|
|
21850
|
+
previous[i] = j;
|
|
21851
|
+
}
|
|
21852
|
+
}
|
|
21853
|
+
if (best === -1 || length[i] > length[best]) best = i;
|
|
21854
|
+
});
|
|
21855
|
+
const stay = /* @__PURE__ */ new Set();
|
|
21856
|
+
for (let i = best; i >= 0; i = previous[i]) stay.add(ids[i]);
|
|
21857
|
+
return stay;
|
|
21858
|
+
}
|
|
21859
|
+
var init_order = __esm({
|
|
21860
|
+
"src/communities/order.ts"() {
|
|
21861
|
+
"use strict";
|
|
21862
|
+
}
|
|
21863
|
+
});
|
|
21864
|
+
|
|
21865
|
+
// src/communities/courses.ts
|
|
21866
|
+
import { isDeepStrictEqual } from "util";
|
|
21867
|
+
import * as path26 from "path";
|
|
21868
|
+
function roomIdOf(rooms, key) {
|
|
21869
|
+
if (key === void 0) return null;
|
|
21870
|
+
const room = rooms.find((entry) => entry.key === key);
|
|
21871
|
+
return room?.id ?? `${PENDING_ROOM}${key}`;
|
|
21872
|
+
}
|
|
21873
|
+
function fileLesson(rooms, lesson) {
|
|
21874
|
+
return lessonContent({
|
|
21875
|
+
title: lesson.title,
|
|
21876
|
+
body: lesson.body,
|
|
21877
|
+
video: lesson.video_url === void 0 ? null : parseCommunityLessonVideoUrl(lesson.video_url),
|
|
21878
|
+
links: lesson.links,
|
|
21879
|
+
room_id: roomIdOf(rooms, lesson.room),
|
|
21880
|
+
requires_paid_plan: lesson.requires_paid_plan,
|
|
21881
|
+
published: lesson.published
|
|
21882
|
+
});
|
|
21883
|
+
}
|
|
21884
|
+
function roomName(rooms, id) {
|
|
21885
|
+
if (id === null) return "none";
|
|
21886
|
+
if (id.startsWith(PENDING_ROOM)) return id.slice(PENDING_ROOM.length);
|
|
21887
|
+
return rooms.find((room) => room.id === id)?.key ?? id;
|
|
21888
|
+
}
|
|
21889
|
+
function roomKeyOf(rooms, roomId) {
|
|
21890
|
+
if (roomId === null) return void 0;
|
|
21891
|
+
const room = rooms.find((entry) => entry.id === roomId);
|
|
21892
|
+
if (!room) throw expected(`room ${roomId} is not one of the rooms ${COMMUNITY_FILE} lists: pull the community first.`);
|
|
21893
|
+
return room.key;
|
|
21894
|
+
}
|
|
21895
|
+
function takeLesson(rooms, lesson, server, fields) {
|
|
21896
|
+
const file = fileLesson(rooms, lesson);
|
|
21897
|
+
const side = lessonContent(server);
|
|
21898
|
+
const take = (field, differs) => differs && (fields === void 0 || fields.has(field));
|
|
21899
|
+
if (take("title", file.title !== side.title)) lesson.title = server.title;
|
|
21900
|
+
if (take("body", file.body !== side.body)) lesson.body = side.body;
|
|
21901
|
+
if (take("video_url", !isDeepStrictEqual(file.video, side.video))) lesson.video_url = side.video ? communityLessonVideoWatchUrl(side.video) : void 0;
|
|
21902
|
+
if (take("links", !isDeepStrictEqual(file.links, side.links))) lesson.links = server.links.map((link) => ({ title: link.title, url: link.url }));
|
|
21903
|
+
if (take("room_id", file.room_id !== side.room_id)) lesson.room = roomKeyOf(rooms, side.room_id);
|
|
21904
|
+
if (take("requires_paid_plan", file.requires_paid_plan !== side.requires_paid_plan)) lesson.requires_paid_plan = side.requires_paid_plan;
|
|
21905
|
+
if (take("published", file.published !== side.published)) lesson.published = side.published;
|
|
21906
|
+
}
|
|
21907
|
+
function takeCourse(folder, server, fields) {
|
|
21908
|
+
const file = courseContent(folder);
|
|
21909
|
+
const side = courseContent(server);
|
|
21910
|
+
const take = (field, differs) => differs && (fields === void 0 || fields.has(field));
|
|
21911
|
+
if (take("title", file.title !== side.title)) folder.title = server.title;
|
|
21912
|
+
if (take("description", file.description !== side.description)) folder.description = server.description;
|
|
21913
|
+
if (take("published", file.published !== side.published)) folder.published = side.published;
|
|
21914
|
+
}
|
|
21915
|
+
function orderVerdict(list, base, server) {
|
|
21916
|
+
const onServer = new Set(server);
|
|
21917
|
+
if (base === void 0) {
|
|
21918
|
+
const shared2 = list.filter((id) => onServer.has(id));
|
|
21919
|
+
return sameOrder(shared2, restrict(server, shared2)) ? "converged" : "conflict";
|
|
21920
|
+
}
|
|
21921
|
+
const inBase = new Set(base);
|
|
21922
|
+
const shared = list.filter((id) => inBase.has(id) && onServer.has(id));
|
|
21923
|
+
const added = list.filter((id) => !inBase.has(id) && onServer.has(id));
|
|
21924
|
+
if (added.length > 0 && !sameGaps(added, shared, list, server)) return "conflict";
|
|
21925
|
+
const was = restrict(base, shared);
|
|
21926
|
+
const now = restrict(server, shared);
|
|
21927
|
+
return threeWay(!sameOrder(shared, was), !sameOrder(now, was), sameOrder(shared, now));
|
|
21928
|
+
}
|
|
21929
|
+
function sameGaps(added, shared, a, b) {
|
|
21930
|
+
const before = (order, id) => new Set(order.slice(0, order.indexOf(id)).filter((other) => shared.includes(other)));
|
|
21931
|
+
const same = (x, y) => x.size === y.size && [...x].every((id) => y.has(id));
|
|
21932
|
+
return sameOrder(restrict(a, added), restrict(b, added)) && added.every((id) => same(before(a, id), before(b, id)));
|
|
21933
|
+
}
|
|
21934
|
+
function placementVerdict(files, base, server) {
|
|
21935
|
+
if (base === void 0) return files === server ? "converged" : "conflict";
|
|
21936
|
+
return threeWay(files !== base, server !== base, files === server);
|
|
21937
|
+
}
|
|
21938
|
+
function planOrder(entries, server, base) {
|
|
21939
|
+
return { state: orderVerdict([...idsOf(entries)], base, server), server: [...server], base: base && [...base] };
|
|
21940
|
+
}
|
|
21941
|
+
function baseModule(synced2, lessonId) {
|
|
21942
|
+
const holding = Object.entries(synced2?.lessons ?? {}).filter(([, ids]) => ids.includes(lessonId));
|
|
21943
|
+
return holding.length === 1 ? holding[0][0] : void 0;
|
|
21944
|
+
}
|
|
21945
|
+
function courseName(files, server, courseId) {
|
|
21946
|
+
const folder = files.courses.find((course) => course.id === courseId);
|
|
21947
|
+
return folder ? `course ${folder.slug}` : `course ${show(server.course.get(courseId)?.course.title ?? courseId)}`;
|
|
21948
|
+
}
|
|
21949
|
+
function planCourses(files, server) {
|
|
21950
|
+
const problems2 = [];
|
|
21951
|
+
const named = idsOf(files.courses);
|
|
21952
|
+
return {
|
|
21953
|
+
server,
|
|
21954
|
+
order: planOrder(files.courses, server.ids, files.synced_courses),
|
|
21955
|
+
courses: files.courses.map((folder) => planCourse(files, folder, server, problems2)),
|
|
21956
|
+
untracked: server.courses.filter((course) => !named.has(course.course.course_id)),
|
|
21957
|
+
problems: problems2
|
|
21958
|
+
};
|
|
21959
|
+
}
|
|
21960
|
+
function planCourse(files, folder, server, problems2) {
|
|
21961
|
+
const found = folder.id === void 0 ? void 0 : server.course.get(folder.id);
|
|
21962
|
+
if (folder.id !== void 0 && !found) problems2.push(`course ${folder.slug}: ${folder.id} is not a course of this community`);
|
|
21963
|
+
const courseId = found?.course.course_id;
|
|
21964
|
+
const plan = {
|
|
21965
|
+
folder,
|
|
21966
|
+
server: null,
|
|
21967
|
+
export: found ?? null,
|
|
21968
|
+
modulesOrder: null,
|
|
21969
|
+
modules: [],
|
|
21970
|
+
lessons: [],
|
|
21971
|
+
untrackedModules: [],
|
|
21972
|
+
untrackedLessons: []
|
|
21973
|
+
};
|
|
21974
|
+
const moduleIds = idsOf(folder.modules);
|
|
21975
|
+
const lessonIds = idsOf(folder.modules.flatMap((module) => module.lessons));
|
|
21976
|
+
if (found) {
|
|
21977
|
+
const side = serverCourse(found.course);
|
|
21978
|
+
plan.server = { state: classify(folder.rev, courseRev(found.course.course_id, courseContent(folder)), side.rev), server: found.course, serverRev: side.rev };
|
|
21979
|
+
plan.modulesOrder = planOrder(folder.modules, found.modules.map((m) => m.module_id), folder.synced?.modules);
|
|
21980
|
+
plan.untrackedModules = found.modules.filter((module) => !moduleIds.has(module.module_id));
|
|
21981
|
+
plan.untrackedLessons = found.modules.flatMap((module) => module.lessons).filter((lesson) => !lessonIds.has(lesson.lesson_id));
|
|
21982
|
+
}
|
|
21983
|
+
const elsewhere = (what, at, id, owner) => {
|
|
21984
|
+
if (owner === void 0) problems2.push(`${what} ${at}: ${id} is not a ${what} of this community`);
|
|
21985
|
+
else problems2.push(`${what} ${at}: ${what} ${id} is one of ${courseName(files, server, owner)}'s on the server, and a ${what} does not move between courses`);
|
|
21986
|
+
};
|
|
21987
|
+
for (const entry of folder.modules) {
|
|
21988
|
+
const at = `${folder.slug}/${entry.key}`;
|
|
21989
|
+
const module = entry.id === void 0 ? void 0 : server.module.get(entry.id);
|
|
21990
|
+
if (entry.id !== void 0 && module?.courseId !== courseId) elsewhere("module", at, entry.id, module?.courseId);
|
|
21991
|
+
const mine = module && module.courseId === courseId ? module.module : void 0;
|
|
21992
|
+
const modulePlan = { course: folder, entry, server: null, lessonsOrder: null };
|
|
21993
|
+
if (mine) {
|
|
21994
|
+
const side = serverModule(mine);
|
|
21995
|
+
modulePlan.server = { state: classify(entry.rev, moduleRev(mine.module_id, moduleContent(entry)), side.rev), server: mine, serverRev: side.rev };
|
|
21996
|
+
modulePlan.lessonsOrder = planOrder(entry.lessons, mine.lessons.map((l) => l.lesson_id), folder.synced?.lessons[mine.module_id]);
|
|
21997
|
+
}
|
|
21998
|
+
plan.modules.push(modulePlan);
|
|
21999
|
+
for (const lesson of entry.lessons) {
|
|
22000
|
+
const where = server.lesson.get(lesson.id ?? "");
|
|
22001
|
+
if (lesson.id !== void 0 && where?.courseId !== courseId) elsewhere("lesson", `${folder.slug}/${lesson.slug}`, lesson.id, where?.courseId);
|
|
22002
|
+
const lessonPlan = { course: folder, module: entry, lesson, server: null, placement: null };
|
|
22003
|
+
if (where && where.courseId === courseId) {
|
|
22004
|
+
const side = serverLesson(where.lesson);
|
|
22005
|
+
lessonPlan.server = { state: classify(lesson.rev, lessonRev(where.lesson.lesson_id, fileLesson(files.rooms, lesson)), side.rev), server: where.lesson, serverRev: side.rev };
|
|
22006
|
+
const filesModule = plannedIds.module(folder, entry);
|
|
22007
|
+
const base = baseModule(folder.synced, where.lesson.lesson_id);
|
|
22008
|
+
lessonPlan.placement = { state: placementVerdict(filesModule, base, where.moduleId), base, files: filesModule, server: where.moduleId };
|
|
22009
|
+
}
|
|
22010
|
+
plan.lessons.push(lessonPlan);
|
|
22011
|
+
}
|
|
22012
|
+
}
|
|
22013
|
+
return plan;
|
|
22014
|
+
}
|
|
22015
|
+
function courseCapProblems(files, plan) {
|
|
22016
|
+
const problems2 = [];
|
|
22017
|
+
const title = (entity) => toLf(entity.title).trim();
|
|
22018
|
+
const courseCreates = plan.courses.filter((course) => course.folder.id === void 0).map((course) => title(course.folder));
|
|
22019
|
+
const courses = plan.server.courses.length;
|
|
22020
|
+
if (courses + certainlyNew(courseCreates, plan.untracked.map((course) => title(course.course))) > COMMUNITY_COURSES_MAX) {
|
|
22021
|
+
problems2.push(`courses: a community holds at most ${COMMUNITY_COURSES_MAX} courses, drafts included; it has ${courses}, and the files create ${courseCreates.length}`);
|
|
22022
|
+
}
|
|
22023
|
+
const named = idsOf(files.courses.flatMap((course) => course.modules.flatMap((module) => module.lessons)));
|
|
22024
|
+
const lessonCreates = [];
|
|
22025
|
+
for (const course of plan.courses) {
|
|
22026
|
+
const moduleCreates = course.modules.filter((module) => module.entry.id === void 0).map((module) => title(module.entry));
|
|
22027
|
+
const creates = course.lessons.filter((lesson) => lesson.lesson.id === void 0).map((lesson) => title(lesson.lesson));
|
|
22028
|
+
lessonCreates.push(...creates);
|
|
22029
|
+
const modules = course.export?.modules.length ?? 0;
|
|
22030
|
+
const lessons2 = course.export?.modules.reduce((n, module) => n + module.lessons.length, 0) ?? 0;
|
|
22031
|
+
if (modules + certainlyNew(moduleCreates, course.untrackedModules.map(title)) > COMMUNITY_COURSE_MODULES_MAX) {
|
|
22032
|
+
problems2.push(`course ${course.folder.slug}: a course holds at most ${COMMUNITY_COURSE_MODULES_MAX} modules; it has ${modules}, and the files create ${moduleCreates.length}`);
|
|
22033
|
+
}
|
|
22034
|
+
if (lessons2 + certainlyNew(creates, course.untrackedLessons.map(title)) > COMMUNITY_COURSE_LESSONS_MAX) {
|
|
22035
|
+
problems2.push(`course ${course.folder.slug}: a course holds at most ${COMMUNITY_COURSE_LESSONS_MAX} lessons, drafts included; it has ${lessons2}, and the files create ${creates.length}`);
|
|
22036
|
+
}
|
|
22037
|
+
}
|
|
22038
|
+
const lessons = plan.server.lesson.size;
|
|
22039
|
+
const untracked = [...plan.server.lesson.values()].filter(({ lesson }) => !named.has(lesson.lesson_id)).map(({ lesson }) => title(lesson));
|
|
22040
|
+
if (lessons + certainlyNew(lessonCreates, untracked) > COMMUNITY_LESSONS_MAX) {
|
|
22041
|
+
problems2.push(`lessons: a community holds at most ${COMMUNITY_LESSONS_MAX} lessons, drafts included; it has ${lessons}, and the files create ${lessonCreates.length}`);
|
|
22042
|
+
}
|
|
22043
|
+
return problems2;
|
|
22044
|
+
}
|
|
22045
|
+
function certainlyNew(creates, untracked) {
|
|
22046
|
+
const left = [...untracked];
|
|
22047
|
+
return creates.filter((title) => {
|
|
22048
|
+
const at = left.indexOf(title);
|
|
22049
|
+
if (at >= 0) left.splice(at, 1);
|
|
22050
|
+
return at < 0;
|
|
22051
|
+
}).length;
|
|
22052
|
+
}
|
|
22053
|
+
function append(order, id) {
|
|
22054
|
+
order.server.push(id);
|
|
22055
|
+
order.pinned?.push(id);
|
|
22056
|
+
order.touched = true;
|
|
22057
|
+
}
|
|
22058
|
+
function insertAfter(list, item, order) {
|
|
22059
|
+
let at = 0;
|
|
22060
|
+
for (let i = order.indexOf(item) - 1; i >= 0; i--) {
|
|
22061
|
+
const k = list.indexOf(order[i]);
|
|
22062
|
+
if (k >= 0) {
|
|
22063
|
+
at = k + 1;
|
|
22064
|
+
break;
|
|
22065
|
+
}
|
|
22066
|
+
}
|
|
22067
|
+
list.splice(at, 0, item);
|
|
22068
|
+
}
|
|
22069
|
+
function shift(order, id, position) {
|
|
22070
|
+
placeAt(order.server, id, position);
|
|
22071
|
+
order.touched = true;
|
|
22072
|
+
}
|
|
22073
|
+
function leave(order, id) {
|
|
22074
|
+
for (const list of [order.server, order.pinned ?? []]) {
|
|
22075
|
+
const at = list.indexOf(id);
|
|
22076
|
+
if (at >= 0) list.splice(at, 1);
|
|
22077
|
+
}
|
|
22078
|
+
order.touched = true;
|
|
22079
|
+
}
|
|
22080
|
+
function parents(files, sim, ids) {
|
|
22081
|
+
const out = [];
|
|
22082
|
+
const listedOf = (entries, id) => [...idsOf(listedIds(entries, id))];
|
|
22083
|
+
out.push({ what: "courses order", kind: "courses", id: files.community_id, order: sim.courses, courseId: null, listed: listedOf(files.courses, ids.course) });
|
|
22084
|
+
for (const course of files.courses) {
|
|
22085
|
+
const courseId = ids.course(course);
|
|
22086
|
+
const order = courseId === void 0 ? void 0 : sim.modules.get(courseId);
|
|
22087
|
+
if (courseId === void 0 || !order) continue;
|
|
22088
|
+
out.push({
|
|
22089
|
+
what: `modules order of course ${course.slug}`,
|
|
22090
|
+
kind: "modules",
|
|
22091
|
+
id: courseId,
|
|
22092
|
+
order,
|
|
22093
|
+
courseId,
|
|
22094
|
+
listed: listedOf(course.modules, (module) => ids.module(course, module))
|
|
22095
|
+
});
|
|
22096
|
+
}
|
|
22097
|
+
for (const course of files.courses) {
|
|
22098
|
+
const courseId = ids.course(course);
|
|
22099
|
+
if (courseId === void 0 || !sim.modules.has(courseId)) continue;
|
|
22100
|
+
for (const module of course.modules) {
|
|
22101
|
+
const moduleId = ids.module(course, module);
|
|
22102
|
+
const order = moduleId === void 0 ? void 0 : sim.lessons.get(moduleId);
|
|
22103
|
+
if (moduleId === void 0 || !order) continue;
|
|
22104
|
+
out.push({
|
|
22105
|
+
what: `lessons order of module ${course.slug}/${module.key}`,
|
|
22106
|
+
kind: "lessons",
|
|
22107
|
+
id: moduleId,
|
|
22108
|
+
order,
|
|
22109
|
+
courseId,
|
|
22110
|
+
listed: listedOf(module.lessons, (lesson) => ids.lesson(course, lesson))
|
|
22111
|
+
});
|
|
22112
|
+
}
|
|
22113
|
+
}
|
|
22114
|
+
return out;
|
|
22115
|
+
}
|
|
22116
|
+
function listedIds(entries, id) {
|
|
22117
|
+
return entries.map((entry) => ({ entry, id: id(entry) }));
|
|
22118
|
+
}
|
|
22119
|
+
function computePlaces(files, sim, ids, placements) {
|
|
22120
|
+
const out = { moves: [], conflicts: [], reorders: [] };
|
|
22121
|
+
const placementOf = (id) => placements.get(id);
|
|
22122
|
+
for (const parent of parents(files, sim, ids)) {
|
|
22123
|
+
const { order } = parent;
|
|
22124
|
+
if (order.conflict) continue;
|
|
22125
|
+
const lessons = parent.kind === "lessons";
|
|
22126
|
+
const wanted = lessons ? parent.listed.filter((id) => placementOf(id)?.state !== "remote") : parent.listed;
|
|
22127
|
+
const leaving = lessons ? order.server.filter((id) => placementOf(id)?.state === "local" && !parent.listed.includes(id)) : [];
|
|
22128
|
+
const arriving = wanted.filter((id) => !order.server.includes(id));
|
|
22129
|
+
if (order.pinned) {
|
|
22130
|
+
const kept = order.pinned;
|
|
22131
|
+
const shared = wanted.filter((id) => kept.includes(id));
|
|
22132
|
+
if (arriving.length > 0 || !sameOrder(shared, restrict(kept, shared))) out.conflicts.push(parent.what);
|
|
22133
|
+
continue;
|
|
22134
|
+
}
|
|
22135
|
+
if (arriving.length === 0 && sameOrder(restrict(order.server, wanted), wanted)) continue;
|
|
22136
|
+
const target = filesOrder(wanted.map((id) => ({ entry: id, id })), order.server, /* @__PURE__ */ new Set([...wanted, ...leaving]));
|
|
22137
|
+
const from = [...order.server];
|
|
22138
|
+
for (const move of reorderMoves(order.server, target)) {
|
|
22139
|
+
if (lessons) {
|
|
22140
|
+
const left = sim.at.get(move.id);
|
|
22141
|
+
if (left === void 0) throw new Error(`computePlaces: lesson ${move.id} is in no module`);
|
|
22142
|
+
if (left !== parent.id) leave(sim.lessons.get(left), move.id);
|
|
22143
|
+
sim.at.set(move.id, parent.id);
|
|
22144
|
+
}
|
|
22145
|
+
shift(order, move.id, move.position);
|
|
22146
|
+
const { id, position } = move;
|
|
22147
|
+
out.moves.push(
|
|
22148
|
+
parent.kind === "courses" ? { kind: "course", id, position } : parent.kind === "modules" ? { kind: "module", id, courseId: parent.id, position } : { kind: "lesson", id, courseId: parent.courseId, moduleId: parent.id, position }
|
|
22149
|
+
);
|
|
22150
|
+
}
|
|
22151
|
+
out.reorders.push({ what: parent.what, from, to: target });
|
|
22152
|
+
}
|
|
22153
|
+
return out;
|
|
22154
|
+
}
|
|
22155
|
+
function outcomeUnknown(err) {
|
|
22156
|
+
return err?.isExpected !== true;
|
|
22157
|
+
}
|
|
22158
|
+
function coursePatch(folder, now, publish) {
|
|
22159
|
+
const file = courseContent(folder);
|
|
22160
|
+
const server = courseContent(now);
|
|
22161
|
+
const body = {
|
|
22162
|
+
...file.title !== server.title ? { title: folder.title } : {},
|
|
22163
|
+
...file.description !== server.description ? { description: folder.description } : {},
|
|
22164
|
+
...file.published !== server.published && (publish || !file.published) ? { published: file.published } : {}
|
|
22165
|
+
};
|
|
22166
|
+
return Object.keys(body).length > 0 ? body : null;
|
|
22167
|
+
}
|
|
22168
|
+
function lessonPatch(rooms, lesson, now, waitForRooms) {
|
|
22169
|
+
const file = fileLesson(rooms, lesson);
|
|
22170
|
+
const server = lessonContent(now);
|
|
22171
|
+
const pending = file.room_id?.startsWith(PENDING_ROOM) ?? false;
|
|
22172
|
+
if (pending && !waitForRooms) throw new Error(`lessonPatch: room ${lesson.room} has no id yet`);
|
|
22173
|
+
const body = {
|
|
22174
|
+
...file.title !== server.title ? { title: lesson.title } : {},
|
|
22175
|
+
...file.body !== server.body ? { body: lesson.body } : {},
|
|
22176
|
+
...!isDeepStrictEqual(file.video, server.video) ? { video_url: lesson.video_url ?? null } : {},
|
|
22177
|
+
...!isDeepStrictEqual(file.links, server.links) ? { links: lesson.links } : {},
|
|
22178
|
+
...file.room_id !== server.room_id && !pending ? { room_id: file.room_id } : {},
|
|
22179
|
+
...file.requires_paid_plan !== server.requires_paid_plan ? { requires_paid_plan: lesson.requires_paid_plan } : {},
|
|
22180
|
+
...file.published !== server.published ? { published: lesson.published } : {}
|
|
22181
|
+
};
|
|
22182
|
+
return Object.keys(body).length > 0 ? body : null;
|
|
22183
|
+
}
|
|
22184
|
+
function courseChanges(from, to) {
|
|
22185
|
+
return [
|
|
22186
|
+
...from.title !== to.title ? [`title ${show(from.title)} \u2192 ${show(to.title)}`] : [],
|
|
22187
|
+
...from.description !== to.description ? ["description"] : [],
|
|
22188
|
+
...from.published !== to.published ? [to.published ? "published" : "unpublished"] : []
|
|
22189
|
+
];
|
|
22190
|
+
}
|
|
22191
|
+
function lessonChanges(from, to, room) {
|
|
22192
|
+
return [
|
|
22193
|
+
...from.title !== to.title ? [`title ${show(from.title)} \u2192 ${show(to.title)}`] : [],
|
|
22194
|
+
...from.body !== to.body ? ["text"] : [],
|
|
22195
|
+
...!isDeepStrictEqual(from.video, to.video) ? [to.video ? from.video ? "video" : "video added" : "video removed"] : [],
|
|
22196
|
+
...!isDeepStrictEqual(from.links, to.links) ? ["links"] : [],
|
|
22197
|
+
...from.room_id !== to.room_id ? [`room ${room(from.room_id)} \u2192 ${room(to.room_id)}`] : [],
|
|
22198
|
+
...from.requires_paid_plan !== to.requires_paid_plan ? [to.requires_paid_plan ? "needs a paid plan" : "no longer needs a paid plan"] : [],
|
|
22199
|
+
...from.published !== to.published ? [to.published ? "published" : "unpublished"] : []
|
|
22200
|
+
];
|
|
22201
|
+
}
|
|
22202
|
+
async function lessonWrite(course, lesson, write) {
|
|
22203
|
+
try {
|
|
22204
|
+
await write();
|
|
22205
|
+
} catch (err) {
|
|
22206
|
+
const reason = extractApiErrorReason(err);
|
|
22207
|
+
if (reason === null || !LESSON_TEXT_REASONS.includes(reason)) throw err;
|
|
22208
|
+
throw new Error(`${extractApiMessage(err)} \u2014 change the lesson's text in ${lessonPath(course, lesson)}`);
|
|
22209
|
+
}
|
|
22210
|
+
}
|
|
22211
|
+
function lessonFromServer(rooms, lesson, slug) {
|
|
22212
|
+
const room = roomKeyOf(rooms, lesson.room_id);
|
|
22213
|
+
return {
|
|
22214
|
+
slug,
|
|
22215
|
+
id: lesson.lesson_id,
|
|
22216
|
+
rev: serverLesson(lesson).rev,
|
|
22217
|
+
title: lesson.title,
|
|
22218
|
+
published: lesson.published,
|
|
22219
|
+
...room === void 0 ? {} : { room },
|
|
22220
|
+
...lesson.video ? { video_url: communityLessonVideoWatchUrl(lesson.video) } : {},
|
|
22221
|
+
links: lesson.links.map((link) => ({ title: link.title, url: link.url })),
|
|
22222
|
+
requires_paid_plan: lesson.requires_paid_plan,
|
|
22223
|
+
body: toLf(lesson.body)
|
|
22224
|
+
};
|
|
22225
|
+
}
|
|
22226
|
+
function moduleFromServer(module, key) {
|
|
22227
|
+
return { key, id: module.module_id, rev: serverModule(module).rev, title: module.title, lessons: [] };
|
|
22228
|
+
}
|
|
22229
|
+
function syncedFromServer(course) {
|
|
22230
|
+
return {
|
|
22231
|
+
modules: course.modules.map((module) => module.module_id),
|
|
22232
|
+
lessons: Object.fromEntries(course.modules.map((module) => [module.module_id, module.lessons.map((lesson) => lesson.lesson_id)]))
|
|
22233
|
+
};
|
|
22234
|
+
}
|
|
22235
|
+
function courseFromServer(rooms, course, slug) {
|
|
22236
|
+
const keys = /* @__PURE__ */ new Set();
|
|
22237
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
22238
|
+
return {
|
|
22239
|
+
slug,
|
|
22240
|
+
id: course.course.course_id,
|
|
22241
|
+
rev: serverCourse(course.course).rev,
|
|
22242
|
+
title: course.course.title,
|
|
22243
|
+
description: course.course.description,
|
|
22244
|
+
published: course.course.published,
|
|
22245
|
+
modules: course.modules.map((module) => {
|
|
22246
|
+
const key = localName(module.title, module.module_id, keys, "module");
|
|
22247
|
+
keys.add(key);
|
|
22248
|
+
const entry = moduleFromServer(module, key);
|
|
22249
|
+
entry.lessons = module.lessons.map((lesson) => {
|
|
22250
|
+
const lessonSlug = localName(lesson.title, lesson.lesson_id, slugs, "lesson");
|
|
22251
|
+
slugs.add(lessonSlug);
|
|
22252
|
+
return lessonFromServer(rooms, lesson, lessonSlug);
|
|
22253
|
+
});
|
|
22254
|
+
return entry;
|
|
22255
|
+
}),
|
|
22256
|
+
synced: syncedFromServer(course),
|
|
22257
|
+
unlisted: []
|
|
22258
|
+
};
|
|
22259
|
+
}
|
|
22260
|
+
function coursesFromServer(files, server) {
|
|
22261
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
22262
|
+
files.courses = server.courses.map((course) => {
|
|
22263
|
+
const slug = localName(course.course.title, course.course.course_id, slugs, "course");
|
|
22264
|
+
slugs.add(slug);
|
|
22265
|
+
return courseFromServer(files.rooms, course, slug);
|
|
22266
|
+
});
|
|
22267
|
+
files.synced_courses = server.ids;
|
|
22268
|
+
}
|
|
22269
|
+
function takeOrder(entries, placed) {
|
|
22270
|
+
const out = [...placed];
|
|
22271
|
+
for (const entry of entries) if (!out.includes(entry)) insertAfter(out, entry, entries);
|
|
22272
|
+
return out;
|
|
22273
|
+
}
|
|
22274
|
+
function mergeArrivals(entries, server, arrivals) {
|
|
22275
|
+
const named = new Set(server.filter((id) => !arrivals.has(id)));
|
|
22276
|
+
return mergeOrder(listed(entries), server, named).flatMap((slot) => "entry" in slot ? [slot.entry] : arrivals.get(slot.untracked) ?? []);
|
|
22277
|
+
}
|
|
22278
|
+
function pullCourses(files, plan, takes, note, listing) {
|
|
22279
|
+
for (const course of plan.courses) pullContent(files, course, takes, note, listing);
|
|
22280
|
+
const tracked = new Map(files.courses.flatMap((course) => course.id === void 0 ? [] : [[course.id, course]]));
|
|
22281
|
+
const held = new Set(files.unlistedCourses.flatMap((folder) => folder.id === null ? [] : [folder.id]));
|
|
22282
|
+
const slugs = new Set([...files.courses.map((course) => course.slug), ...files.unlistedCourses.map((folder) => folder.name)].map((name) => name.toLowerCase()));
|
|
22283
|
+
const courseFor = (id) => {
|
|
22284
|
+
const known = tracked.get(id);
|
|
22285
|
+
if (known) return known;
|
|
22286
|
+
const course = plan.server.course.get(id);
|
|
22287
|
+
if (held.has(id)) {
|
|
22288
|
+
listing.kept.push(`course ${show(course.course.title)} (${id}): its folder is not listed in ${COMMUNITY_FILE} courses \u2014 list it to pull it`);
|
|
22289
|
+
return null;
|
|
22290
|
+
}
|
|
22291
|
+
const slug = localName(course.course.title, id, slugs, "course");
|
|
22292
|
+
slugs.add(slug);
|
|
22293
|
+
listing.changes.push(`+ course ${slug}: ${show(course.course.title)}`);
|
|
22294
|
+
return courseFromServer(files.rooms, course, slug);
|
|
22295
|
+
};
|
|
22296
|
+
const order = plan.order;
|
|
22297
|
+
if (takes(order.state)) {
|
|
22298
|
+
files.courses = takeOrder(files.courses, order.server.flatMap((id) => courseFor(id) ?? []));
|
|
22299
|
+
listing.changes.push("~ courses order");
|
|
22300
|
+
} else {
|
|
22301
|
+
const arrivals = new Map(order.server.filter((id) => !tracked.has(id)).flatMap((id) => {
|
|
22302
|
+
const course = courseFor(id);
|
|
22303
|
+
return course ? [[id, course]] : [];
|
|
22304
|
+
}));
|
|
22305
|
+
files.courses = mergeArrivals(files.courses, order.server, arrivals);
|
|
22306
|
+
note(order.state, "courses order");
|
|
22307
|
+
}
|
|
22308
|
+
if (synced(order.state, takes)) files.synced_courses = [...order.server];
|
|
22309
|
+
for (const course of plan.courses) if (course.export) pullTree(files, plan.server, course, takes, note, listing);
|
|
22310
|
+
}
|
|
22311
|
+
function pullContent(files, course, takes, note, listing) {
|
|
22312
|
+
const { folder } = course;
|
|
22313
|
+
if (course.server) {
|
|
22314
|
+
const { state, server, serverRev } = course.server;
|
|
22315
|
+
if (takes(state)) {
|
|
22316
|
+
listing.changes.push(`~ course ${folder.slug}: ${courseChanges(courseContent(folder), courseContent(server)).join("; ")}`);
|
|
22317
|
+
takeCourse(folder, server);
|
|
22318
|
+
}
|
|
22319
|
+
if (takes(state) || state === "converged") folder.rev = serverRev;
|
|
22320
|
+
note(state, `course ${folder.slug}`);
|
|
22321
|
+
} else if (folder.id === void 0) {
|
|
22322
|
+
listing.kept.push(`course ${folder.slug} (to create)`);
|
|
22323
|
+
}
|
|
22324
|
+
for (const module of course.modules) {
|
|
22325
|
+
const at = `module ${folder.slug}/${module.entry.key}`;
|
|
22326
|
+
if (!module.server) {
|
|
22327
|
+
if (module.entry.id === void 0) listing.kept.push(`${at} (to create)`);
|
|
22328
|
+
continue;
|
|
22329
|
+
}
|
|
22330
|
+
const { state, server, serverRev } = module.server;
|
|
22331
|
+
if (takes(state)) {
|
|
22332
|
+
listing.changes.push(`~ ${at}: title ${show(module.entry.title)} \u2192 ${show(server.title)}`);
|
|
22333
|
+
if (moduleContent(module.entry).title !== moduleContent(server).title) module.entry.title = server.title;
|
|
22334
|
+
}
|
|
22335
|
+
if (takes(state) || state === "converged") module.entry.rev = serverRev;
|
|
22336
|
+
note(state, at);
|
|
22337
|
+
}
|
|
22338
|
+
for (const lesson of course.lessons) {
|
|
22339
|
+
const at = `lesson ${folder.slug}/${lesson.lesson.slug}`;
|
|
22340
|
+
if (!lesson.server) {
|
|
22341
|
+
if (lesson.lesson.id === void 0) listing.kept.push(`${at} (to create)`);
|
|
22342
|
+
continue;
|
|
22343
|
+
}
|
|
22344
|
+
const { state, server, serverRev } = lesson.server;
|
|
22345
|
+
if (takes(state)) {
|
|
22346
|
+
const changes = lessonChanges(fileLesson(files.rooms, lesson.lesson), lessonContent(server), (id) => roomName(files.rooms, id));
|
|
22347
|
+
listing.changes.push(`~ ${at}: ${changes.join("; ")}`);
|
|
22348
|
+
takeLesson(files.rooms, lesson.lesson, server);
|
|
22349
|
+
}
|
|
22350
|
+
if (takes(state) || state === "converged") lesson.lesson.rev = serverRev;
|
|
22351
|
+
note(state, at);
|
|
22352
|
+
}
|
|
22353
|
+
}
|
|
22354
|
+
function pullTree(files, index, course, takes, note, listing) {
|
|
22355
|
+
const { folder } = course;
|
|
22356
|
+
const server = course.export;
|
|
22357
|
+
const keys = new Set(folder.modules.map((module) => module.key));
|
|
22358
|
+
const unlisted = folder.unlisted.map((file) => ({ slug: path26.basename(file.name, LESSON_FILE_EXT), id: file.id }));
|
|
22359
|
+
const slugs = new Set([...folder.modules.flatMap((module) => module.lessons.map((lesson) => lesson.slug)), ...unlisted.map((file) => file.slug)].map((name) => name.toLowerCase()));
|
|
22360
|
+
const held = new Set(unlisted.flatMap((file) => file.id === null ? [] : [file.id]));
|
|
22361
|
+
const fresh = /* @__PURE__ */ new Set();
|
|
22362
|
+
const moduleFor = (id) => {
|
|
22363
|
+
const known = folder.modules.find((module2) => module2.id === id);
|
|
22364
|
+
if (known) return known;
|
|
22365
|
+
const { module } = index.module.get(id);
|
|
22366
|
+
const key = localName(module.title, id, keys, "module");
|
|
22367
|
+
keys.add(key);
|
|
22368
|
+
listing.changes.push(`+ module ${folder.slug}/${key}: ${show(module.title)}`);
|
|
22369
|
+
const entry = moduleFromServer(module, key);
|
|
22370
|
+
fresh.add(entry);
|
|
22371
|
+
return entry;
|
|
22372
|
+
};
|
|
22373
|
+
const modulesOrder = course.modulesOrder;
|
|
22374
|
+
const what = `modules order of course ${folder.slug}`;
|
|
22375
|
+
if (takes(modulesOrder.state)) {
|
|
22376
|
+
folder.modules = takeOrder(folder.modules, modulesOrder.server.flatMap((id) => moduleFor(id) ?? []));
|
|
22377
|
+
listing.changes.push(`~ ${what}`);
|
|
22378
|
+
} else {
|
|
22379
|
+
const arrivals = new Map(modulesOrder.server.filter((id) => !folder.modules.some((m) => m.id === id)).flatMap((id) => {
|
|
22380
|
+
const entry = moduleFor(id);
|
|
22381
|
+
return entry ? [[id, entry]] : [];
|
|
22382
|
+
}));
|
|
22383
|
+
folder.modules = mergeArrivals(folder.modules, modulesOrder.server, arrivals);
|
|
22384
|
+
note(modulesOrder.state, what);
|
|
22385
|
+
}
|
|
22386
|
+
const entryOf = (moduleId) => folder.modules.find((module) => module.id === moduleId);
|
|
22387
|
+
const home = /* @__PURE__ */ new Map();
|
|
22388
|
+
const keptApart = /* @__PURE__ */ new Set();
|
|
22389
|
+
for (const lesson of course.lessons) {
|
|
22390
|
+
const { placement } = lesson;
|
|
22391
|
+
if (!placement) {
|
|
22392
|
+
home.set(lesson.lesson, lesson.module);
|
|
22393
|
+
continue;
|
|
22394
|
+
}
|
|
22395
|
+
const at = `lesson ${folder.slug}/${lesson.lesson.slug}`;
|
|
22396
|
+
if (placement.state === "conflict") note("conflict", `${at}: moved in the files and on the server`);
|
|
22397
|
+
if (placement.state === "local") listing.kept.push(`${at}: moved to module ${lesson.module.key}`);
|
|
22398
|
+
const taken = takes(placement.state) && placement.state !== "converged";
|
|
22399
|
+
const to = taken ? entryOf(placement.server) : void 0;
|
|
22400
|
+
if (to && to !== lesson.module) listing.changes.push(`~ ${at}: moved to module ${to.key}`);
|
|
22401
|
+
if (placement.state === "conflict" && !taken) keptApart.add(lesson);
|
|
22402
|
+
home.set(lesson.lesson, to ?? lesson.module);
|
|
22403
|
+
}
|
|
22404
|
+
const named = idsOf(course.lessons.map((lesson) => lesson.lesson));
|
|
22405
|
+
for (const module of server.modules) {
|
|
22406
|
+
const entry = entryOf(module.module_id);
|
|
22407
|
+
if (!entry) continue;
|
|
22408
|
+
for (const lesson of module.lessons) {
|
|
22409
|
+
if (named.has(lesson.lesson_id)) continue;
|
|
22410
|
+
if (held.has(lesson.lesson_id)) {
|
|
22411
|
+
listing.kept.push(`lesson ${show(lesson.title)} (${lesson.lesson_id}) of course ${folder.slug}: its file is listed by no module \u2014 list it to pull it`);
|
|
22412
|
+
continue;
|
|
22413
|
+
}
|
|
22414
|
+
const slug = localName(lesson.title, lesson.lesson_id, slugs, "lesson");
|
|
22415
|
+
slugs.add(slug);
|
|
22416
|
+
listing.changes.push(`+ lesson ${folder.slug}/${slug}: ${show(lesson.title)}`);
|
|
22417
|
+
home.set(lessonFromServer(files.rooms, lesson, slug), entry);
|
|
22418
|
+
}
|
|
22419
|
+
}
|
|
22420
|
+
for (const entry of folder.modules) {
|
|
22421
|
+
const members = [...home].filter(([, at]) => at === entry).map(([lesson]) => lesson);
|
|
22422
|
+
const own = entry.lessons.filter((lesson) => lesson.id === void 0 || members.includes(lesson));
|
|
22423
|
+
const serverIds = entry.id === void 0 ? [] : index.module.get(entry.id)?.module.lessons.map((lesson) => lesson.lesson_id) ?? [];
|
|
22424
|
+
const plan = course.modules.find((m) => m.entry === entry);
|
|
22425
|
+
const take = fresh.has(entry) || plan?.lessonsOrder !== null && plan?.lessonsOrder !== void 0 && takes(plan.lessonsOrder.state);
|
|
22426
|
+
if (take) {
|
|
22427
|
+
const byId = new Map(members.flatMap((lesson) => lesson.id === void 0 ? [] : [[lesson.id, lesson]]));
|
|
22428
|
+
entry.lessons = takeOrder(own, serverIds.flatMap((id) => byId.get(id) ?? []));
|
|
22429
|
+
if (!fresh.has(entry)) listing.changes.push(`~ lessons order of module ${folder.slug}/${entry.key}`);
|
|
22430
|
+
} else {
|
|
22431
|
+
const arrivals = new Map(members.filter((lesson) => !own.includes(lesson)).flatMap((lesson) => lesson.id === void 0 ? [] : [[lesson.id, lesson]]));
|
|
22432
|
+
entry.lessons = mergeArrivals(own, serverIds, arrivals);
|
|
22433
|
+
if (plan?.lessonsOrder) note(plan.lessonsOrder.state, `lessons order of module ${folder.slug}/${entry.key}`);
|
|
22434
|
+
}
|
|
22435
|
+
}
|
|
22436
|
+
const was = folder.synced;
|
|
22437
|
+
const lessons = {};
|
|
22438
|
+
for (const module of server.modules) {
|
|
22439
|
+
const verdict = course.modules.find((m) => m.entry.id === module.module_id)?.lessonsOrder;
|
|
22440
|
+
if (verdict && !synced(verdict.state, takes) && was?.lessons[module.module_id]) {
|
|
22441
|
+
lessons[module.module_id] = was.lessons[module.module_id];
|
|
22442
|
+
continue;
|
|
22443
|
+
}
|
|
22444
|
+
const kept = [...keptApart].filter((lesson) => lesson.placement.server === module.module_id).map((lesson) => lesson.lesson.id);
|
|
22445
|
+
const based = [...keptApart].filter((lesson) => lesson.placement.base === module.module_id).map((lesson) => lesson.lesson.id);
|
|
22446
|
+
const ids = module.lessons.map((lesson) => lesson.lesson_id).filter((id) => !kept.includes(id));
|
|
22447
|
+
for (const id of based) if (!ids.includes(id)) insertAfter(ids, id, was?.lessons[module.module_id] ?? []);
|
|
22448
|
+
lessons[module.module_id] = ids;
|
|
22449
|
+
}
|
|
22450
|
+
folder.synced = {
|
|
22451
|
+
modules: synced(modulesOrder.state, takes) || !was?.modules ? server.modules.map((module) => module.module_id) : was.modules,
|
|
22452
|
+
lessons
|
|
22453
|
+
};
|
|
22454
|
+
}
|
|
22455
|
+
function coursesChangedSince(before, after) {
|
|
22456
|
+
const states = (server) => {
|
|
22457
|
+
const out = /* @__PURE__ */ new Map();
|
|
22458
|
+
out.set("courses", { label: "courses order", rev: server.ids.join() });
|
|
22459
|
+
for (const course of server.courses) {
|
|
22460
|
+
const courseId = course.course.course_id;
|
|
22461
|
+
const label = `course ${show(course.course.title)}`;
|
|
22462
|
+
out.set(courseId, { label, rev: serverCourse(course.course).rev });
|
|
22463
|
+
out.set(`modules:${courseId}`, { label, rev: course.modules.map((m) => m.module_id).join() });
|
|
22464
|
+
for (const module of course.modules) {
|
|
22465
|
+
const moduleLabel = `module ${show(module.title)}`;
|
|
22466
|
+
out.set(module.module_id, { label: moduleLabel, rev: serverModule(module).rev });
|
|
22467
|
+
out.set(`lessons:${module.module_id}`, { label: moduleLabel, rev: module.lessons.map((l) => l.lesson_id).join() });
|
|
22468
|
+
for (const lesson of module.lessons) out.set(lesson.lesson_id, { label: `lesson ${show(lesson.title)}`, rev: serverLesson(lesson).rev });
|
|
22469
|
+
}
|
|
22470
|
+
}
|
|
22471
|
+
return out;
|
|
22472
|
+
};
|
|
22473
|
+
const was = states(before);
|
|
22474
|
+
const changed = /* @__PURE__ */ new Set();
|
|
22475
|
+
for (const [key, now] of states(after)) if (was.get(key)?.rev !== now.rev) changed.add(now.label);
|
|
22476
|
+
return [...changed];
|
|
22477
|
+
}
|
|
22478
|
+
var ServerCourses, PENDING_ROOM, listed, Sim, freshOrder, realIds, plannedIds, LESSON_TEXT_REASONS, CoursePush, synced;
|
|
22479
|
+
var init_courses = __esm({
|
|
22480
|
+
"src/communities/courses.ts"() {
|
|
22481
|
+
"use strict";
|
|
22482
|
+
init_contracts();
|
|
22483
|
+
init_community_workspace();
|
|
22484
|
+
init_errors2();
|
|
22485
|
+
init_expected();
|
|
22486
|
+
init_course_files();
|
|
22487
|
+
init_listing();
|
|
22488
|
+
init_model();
|
|
22489
|
+
init_order();
|
|
22490
|
+
ServerCourses = class {
|
|
22491
|
+
constructor(courses) {
|
|
22492
|
+
this.courses = courses;
|
|
22493
|
+
for (const course of courses) {
|
|
22494
|
+
const courseId = course.course.course_id;
|
|
22495
|
+
this.course.set(courseId, course);
|
|
22496
|
+
for (const module of course.modules) {
|
|
22497
|
+
this.module.set(module.module_id, { courseId, module });
|
|
22498
|
+
for (const lesson of module.lessons) this.lesson.set(lesson.lesson_id, { courseId, moduleId: module.module_id, lesson });
|
|
22499
|
+
}
|
|
22500
|
+
}
|
|
22501
|
+
}
|
|
22502
|
+
course = /* @__PURE__ */ new Map();
|
|
22503
|
+
module = /* @__PURE__ */ new Map();
|
|
22504
|
+
lesson = /* @__PURE__ */ new Map();
|
|
22505
|
+
get ids() {
|
|
22506
|
+
return this.courses.map((course) => course.course.course_id);
|
|
22507
|
+
}
|
|
22508
|
+
};
|
|
22509
|
+
PENDING_ROOM = "pending:";
|
|
22510
|
+
listed = (entries) => listedIds(entries, (entry) => entry.id);
|
|
22511
|
+
Sim = class _Sim {
|
|
22512
|
+
constructor(courses, modules = /* @__PURE__ */ new Map(), lessons = /* @__PURE__ */ new Map(), at = /* @__PURE__ */ new Map()) {
|
|
22513
|
+
this.courses = courses;
|
|
22514
|
+
this.modules = modules;
|
|
22515
|
+
this.lessons = lessons;
|
|
22516
|
+
this.at = at;
|
|
22517
|
+
}
|
|
22518
|
+
static from(plan) {
|
|
22519
|
+
const order = (ids, verdict) => ({
|
|
22520
|
+
server: [...ids],
|
|
22521
|
+
pinned: verdict?.state === "remote" ? [...verdict.base ?? []] : null,
|
|
22522
|
+
conflict: verdict?.state === "conflict",
|
|
22523
|
+
touched: false
|
|
22524
|
+
});
|
|
22525
|
+
const sim = new _Sim(order(plan.order.server, plan.order));
|
|
22526
|
+
for (const course of plan.server.courses) {
|
|
22527
|
+
const courseId = course.course.course_id;
|
|
22528
|
+
const tracked = plan.courses.find((c) => c.export?.course.course_id === courseId);
|
|
22529
|
+
sim.modules.set(courseId, order(course.modules.map((m) => m.module_id), tracked?.modulesOrder));
|
|
22530
|
+
for (const module of course.modules) {
|
|
22531
|
+
const entry = tracked?.modules.find((m) => m.server?.server.module_id === module.module_id);
|
|
22532
|
+
sim.lessons.set(module.module_id, order(module.lessons.map((l) => l.lesson_id), entry?.lessonsOrder));
|
|
22533
|
+
for (const lesson of module.lessons) sim.at.set(lesson.lesson_id, module.module_id);
|
|
22534
|
+
}
|
|
22535
|
+
}
|
|
22536
|
+
return sim;
|
|
22537
|
+
}
|
|
22538
|
+
clone() {
|
|
22539
|
+
const copy = (order) => ({ ...order, server: [...order.server], pinned: order.pinned && [...order.pinned] });
|
|
22540
|
+
const map = (orders) => new Map([...orders].map(([id, order]) => [id, copy(order)]));
|
|
22541
|
+
return new _Sim(copy(this.courses), map(this.modules), map(this.lessons), new Map(this.at));
|
|
22542
|
+
}
|
|
22543
|
+
};
|
|
22544
|
+
freshOrder = () => ({ server: [], pinned: null, conflict: false, touched: true });
|
|
22545
|
+
realIds = { course: (c) => c.id, module: (_c, m) => m.id, lesson: (_c, l) => l.id };
|
|
22546
|
+
plannedIds = {
|
|
22547
|
+
course: (c) => c.id ?? `new:course:${c.slug}`,
|
|
22548
|
+
module: (c, m) => m.id ?? `new:module:${c.slug}/${m.key}`,
|
|
22549
|
+
lesson: (c, l) => l.id ?? `new:lesson:${c.slug}/${l.slug}`
|
|
22550
|
+
};
|
|
22551
|
+
LESSON_TEXT_REASONS = [
|
|
22552
|
+
COMMUNITY_LESSON_LINE_PREFIX_TOO_WIDE_REASON,
|
|
22553
|
+
COMMUNITY_LESSON_TREE_TOO_DEEP_REASON,
|
|
22554
|
+
COMMUNITY_LESSON_TREE_TOO_LARGE_REASON
|
|
22555
|
+
];
|
|
22556
|
+
CoursePush = class {
|
|
22557
|
+
constructor(files, plan, remote, save) {
|
|
22558
|
+
this.files = files;
|
|
22559
|
+
this.plan = plan;
|
|
22560
|
+
this.remote = remote;
|
|
22561
|
+
this.save = save;
|
|
22562
|
+
this.sim = Sim.from(plan);
|
|
22563
|
+
for (const lesson of plan.courses.flatMap((course) => course.lessons)) {
|
|
22564
|
+
const { placement } = lesson;
|
|
22565
|
+
if (!placement || lesson.lesson.id === void 0) continue;
|
|
22566
|
+
this.placements.set(lesson.lesson.id, placement);
|
|
22567
|
+
if (placement.state !== "remote") continue;
|
|
22568
|
+
this.movedIn.set(placement.server, /* @__PURE__ */ new Set([...this.movedIn.get(placement.server) ?? [], lesson.lesson.id]));
|
|
22569
|
+
if (placement.base !== void 0) this.movedOut.set(placement.base, [...this.movedOut.get(placement.base) ?? [], lesson.lesson.id]);
|
|
22570
|
+
}
|
|
22571
|
+
for (const course of plan.server.courses) {
|
|
22572
|
+
this.courseNow.set(course.course.course_id, course.course);
|
|
22573
|
+
this.names.set(course.course.course_id, show(course.course.title));
|
|
22574
|
+
for (const module of course.modules) {
|
|
22575
|
+
this.moduleNow.set(module.module_id, module);
|
|
22576
|
+
this.names.set(module.module_id, show(module.title));
|
|
22577
|
+
for (const lesson of module.lessons) {
|
|
22578
|
+
this.lessonNow.set(lesson.lesson_id, lesson);
|
|
22579
|
+
this.names.set(lesson.lesson_id, show(lesson.title));
|
|
22580
|
+
}
|
|
22581
|
+
}
|
|
22582
|
+
}
|
|
22583
|
+
for (const course of files.courses) {
|
|
22584
|
+
this.names.set(plannedIds.course(course), course.slug);
|
|
22585
|
+
for (const module of course.modules) {
|
|
22586
|
+
this.names.set(plannedIds.module(course, module), module.key);
|
|
22587
|
+
for (const lesson of module.lessons) this.names.set(plannedIds.lesson(course, lesson), lesson.slug);
|
|
22588
|
+
}
|
|
22589
|
+
}
|
|
22590
|
+
this.predicted = this.predict();
|
|
22591
|
+
}
|
|
22592
|
+
sim;
|
|
22593
|
+
courseNow = /* @__PURE__ */ new Map();
|
|
22594
|
+
moduleNow = /* @__PURE__ */ new Map();
|
|
22595
|
+
lessonNow = /* @__PURE__ */ new Map();
|
|
22596
|
+
names = /* @__PURE__ */ new Map();
|
|
22597
|
+
/** Files whose synced lists changed since they were last saved. */
|
|
22598
|
+
unsaved = /* @__PURE__ */ new Set();
|
|
22599
|
+
/** Each listed lesson's place's verdict, by id. */
|
|
22600
|
+
placements = /* @__PURE__ */ new Map();
|
|
22601
|
+
/** The lessons the server moved since, which push leaves where they are: by module, those it moved in and out. */
|
|
22602
|
+
movedIn = /* @__PURE__ */ new Map();
|
|
22603
|
+
movedOut = /* @__PURE__ */ new Map();
|
|
22604
|
+
/** The places phase as the plan predicts it, every create landing as a new entity. */
|
|
22605
|
+
predicted;
|
|
22606
|
+
get communityId() {
|
|
22607
|
+
return this.files.community_id;
|
|
22608
|
+
}
|
|
22609
|
+
lessons() {
|
|
22610
|
+
return this.plan.courses.flatMap((course) => course.lessons);
|
|
22611
|
+
}
|
|
22612
|
+
/** The places phase on a copy of the server's orders where every create has landed, last. */
|
|
22613
|
+
predict() {
|
|
22614
|
+
const sim = this.sim.clone();
|
|
22615
|
+
for (const course of this.files.courses) {
|
|
22616
|
+
const courseId = plannedIds.course(course);
|
|
22617
|
+
if (course.id === void 0) {
|
|
22618
|
+
append(sim.courses, courseId);
|
|
22619
|
+
sim.modules.set(courseId, freshOrder());
|
|
22620
|
+
}
|
|
22621
|
+
for (const module of course.modules) {
|
|
22622
|
+
const moduleId = plannedIds.module(course, module);
|
|
22623
|
+
if (module.id === void 0) {
|
|
22624
|
+
const modules = sim.modules.get(courseId);
|
|
22625
|
+
if (!modules) continue;
|
|
22626
|
+
append(modules, moduleId);
|
|
22627
|
+
sim.lessons.set(moduleId, freshOrder());
|
|
22628
|
+
}
|
|
22629
|
+
for (const lesson of module.lessons) {
|
|
22630
|
+
const lessons = sim.lessons.get(moduleId);
|
|
22631
|
+
if (lesson.id !== void 0 || !lessons) continue;
|
|
22632
|
+
const lessonId = plannedIds.lesson(course, lesson);
|
|
22633
|
+
append(lessons, lessonId);
|
|
22634
|
+
sim.at.set(lessonId, moduleId);
|
|
22635
|
+
}
|
|
22636
|
+
}
|
|
22637
|
+
}
|
|
22638
|
+
return computePlaces(this.files, sim, plannedIds, this.placements);
|
|
22639
|
+
}
|
|
22640
|
+
// ── The listing ──
|
|
22641
|
+
roomName = (id) => roomName(this.files.rooms, id);
|
|
22642
|
+
/** Push's listing of the courses, server → files. */
|
|
22643
|
+
list(listing) {
|
|
22644
|
+
const { plan, files } = this;
|
|
22645
|
+
for (const course of plan.courses) {
|
|
22646
|
+
const { folder } = course;
|
|
22647
|
+
if (!course.server) {
|
|
22648
|
+
listing.changes.push(`+ course ${folder.slug}: create ${show(folder.title)}, ${folder.published ? "published once its lessons are in place" : "a draft"}`);
|
|
22649
|
+
} else if (course.server.state === "local") {
|
|
22650
|
+
listing.changes.push(`~ course ${folder.slug}: ${courseChanges(courseContent(course.server.server), courseContent(folder)).join("; ")}`);
|
|
22651
|
+
}
|
|
22652
|
+
for (const module of course.modules) {
|
|
22653
|
+
const at = `${folder.slug}/${module.entry.key}`;
|
|
22654
|
+
if (!module.server) listing.changes.push(`+ module ${at}: create ${show(module.entry.title)}`);
|
|
22655
|
+
else if (module.server.state === "local") listing.changes.push(`~ module ${at}: title ${show(module.server.server.title)} \u2192 ${show(module.entry.title)}`);
|
|
22656
|
+
}
|
|
22657
|
+
for (const lesson of course.lessons) {
|
|
22658
|
+
const at = `${folder.slug}/${lesson.lesson.slug}`;
|
|
22659
|
+
const file = fileLesson(files.rooms, lesson.lesson);
|
|
22660
|
+
if (!lesson.server) {
|
|
22661
|
+
const flags = [...file.published ? ["published"] : [], ...file.room_id ? [`room ${this.roomName(file.room_id)}`] : []];
|
|
22662
|
+
listing.changes.push(`+ lesson ${at}: create ${show(lesson.lesson.title)} in module ${lesson.module.key}${flags.length > 0 ? `, ${flags.join(", ")}` : ""}`);
|
|
22663
|
+
} else if (lesson.server.state === "local") {
|
|
22664
|
+
const server = lessonContent(lesson.server.server);
|
|
22665
|
+
listing.changes.push(`~ lesson ${at}: ${lessonChanges(server, file, this.roomName).join("; ")}`);
|
|
22666
|
+
if (server.body !== file.body) listing.changes.push(...lineDiff(server.body, file.body).map((line) => ` ${line}`));
|
|
22667
|
+
}
|
|
22668
|
+
}
|
|
22669
|
+
}
|
|
22670
|
+
for (const reorder of this.predicted.reorders) {
|
|
22671
|
+
listing.changes.push(`~ ${reorder.what}: ${reorder.from.map(this.name).join(", ")} \u2192 ${reorder.to.map(this.name).join(", ")}`);
|
|
22672
|
+
}
|
|
22673
|
+
const verdicts = (state) => [
|
|
22674
|
+
...plan.order.state === state ? ["courses order"] : [],
|
|
22675
|
+
...plan.courses.flatMap((course) => [
|
|
22676
|
+
...course.server?.state === state ? [`course ${course.folder.slug}`] : [],
|
|
22677
|
+
...course.modulesOrder?.state === state ? [`modules order of course ${course.folder.slug}`] : [],
|
|
22678
|
+
...course.modules.flatMap((module) => [
|
|
22679
|
+
...module.server?.state === state ? [`module ${course.folder.slug}/${module.entry.key}`] : [],
|
|
22680
|
+
...module.lessonsOrder?.state === state ? [`lessons order of module ${course.folder.slug}/${module.entry.key}`] : []
|
|
22681
|
+
]),
|
|
22682
|
+
...course.lessons.flatMap((lesson) => [
|
|
22683
|
+
...lesson.server?.state === state ? [`lesson ${course.folder.slug}/${lesson.lesson.slug}`] : [],
|
|
22684
|
+
...lesson.placement?.state === state ? [`lesson ${course.folder.slug}/${lesson.lesson.slug}: ${state === "conflict" ? "moved in the files and on the server" : `moved to module ${this.pathOf(lesson.placement.server)}`}`] : []
|
|
22685
|
+
])
|
|
22686
|
+
])
|
|
22687
|
+
];
|
|
22688
|
+
listing.serverChanged.push(...verdicts("remote"));
|
|
22689
|
+
listing.conflicts.push(.../* @__PURE__ */ new Set([...verdicts("conflict"), ...this.predicted.conflicts]));
|
|
22690
|
+
listing.leftAsIs.push(
|
|
22691
|
+
...plan.untracked.map((course) => `course ${show(course.course.title)} (${course.course.course_id})`),
|
|
22692
|
+
...plan.courses.flatMap((course) => [
|
|
22693
|
+
...course.untrackedModules.map((module) => `module ${show(module.title)} (${module.module_id}) of course ${course.folder.slug}`),
|
|
22694
|
+
...course.untrackedLessons.map((lesson) => `lesson ${show(lesson.title)} (${lesson.lesson_id}) of course ${course.folder.slug}`)
|
|
22695
|
+
])
|
|
22696
|
+
);
|
|
22697
|
+
}
|
|
22698
|
+
name = (id) => this.names.get(id) ?? id;
|
|
22699
|
+
/** Revs to refresh without a write: both sides agree, or the files carried no base. Saves the files they live in. */
|
|
22700
|
+
refreshConverged() {
|
|
22701
|
+
const { plan, files } = this;
|
|
22702
|
+
let refreshed = 0;
|
|
22703
|
+
const refresh = (converged, owner, set) => {
|
|
22704
|
+
if (!converged) return;
|
|
22705
|
+
set();
|
|
22706
|
+
this.unsaved.add(owner);
|
|
22707
|
+
refreshed++;
|
|
22708
|
+
};
|
|
22709
|
+
const rebase = (converged, order) => {
|
|
22710
|
+
if (!converged || !order) return;
|
|
22711
|
+
order.touched = true;
|
|
22712
|
+
refreshed++;
|
|
22713
|
+
};
|
|
22714
|
+
rebase(plan.order.state === "converged", this.sim.courses);
|
|
22715
|
+
for (const course of plan.courses) {
|
|
22716
|
+
const { folder } = course;
|
|
22717
|
+
refresh(course.server?.state === "converged", folder, () => {
|
|
22718
|
+
folder.rev = course.server.serverRev;
|
|
22719
|
+
});
|
|
22720
|
+
if (folder.id !== void 0) rebase(course.modulesOrder?.state === "converged", this.sim.modules.get(folder.id));
|
|
22721
|
+
for (const module of course.modules) {
|
|
22722
|
+
refresh(module.server?.state === "converged", folder, () => {
|
|
22723
|
+
module.entry.rev = module.server.serverRev;
|
|
22724
|
+
});
|
|
22725
|
+
if (module.entry.id !== void 0) rebase(module.lessonsOrder?.state === "converged", this.sim.lessons.get(module.entry.id));
|
|
22726
|
+
}
|
|
22727
|
+
for (const lesson of course.lessons) {
|
|
22728
|
+
const placement = lesson.placement;
|
|
22729
|
+
if (placement?.state !== "converged") continue;
|
|
22730
|
+
const was = placement.base === void 0 ? void 0 : this.sim.lessons.get(placement.base);
|
|
22731
|
+
if (was) {
|
|
22732
|
+
const at = was.pinned?.indexOf(lesson.lesson.id) ?? -1;
|
|
22733
|
+
if (at >= 0) was.pinned.splice(at, 1);
|
|
22734
|
+
was.touched = true;
|
|
22735
|
+
}
|
|
22736
|
+
const now = this.sim.lessons.get(placement.server);
|
|
22737
|
+
if (now) {
|
|
22738
|
+
if (now.pinned && !now.pinned.includes(lesson.lesson.id)) insertAfter(now.pinned, lesson.lesson.id, lesson.module.lessons.map((l) => l.id ?? ""));
|
|
22739
|
+
now.touched = true;
|
|
22740
|
+
}
|
|
22741
|
+
refreshed++;
|
|
22742
|
+
}
|
|
22743
|
+
for (const lesson of course.lessons) {
|
|
22744
|
+
if (lesson.server?.state === "converged") {
|
|
22745
|
+
lesson.lesson.rev = lesson.server.serverRev;
|
|
22746
|
+
this.save.lesson(folder, lesson.lesson);
|
|
22747
|
+
refreshed++;
|
|
22748
|
+
}
|
|
22749
|
+
}
|
|
22750
|
+
}
|
|
22751
|
+
this.settle();
|
|
22752
|
+
return refreshed;
|
|
22753
|
+
}
|
|
22754
|
+
/** Save the files whose synced lists changed. */
|
|
22755
|
+
flush() {
|
|
22756
|
+
for (const owner of this.unsaved) {
|
|
22757
|
+
if (owner === null) this.save.community();
|
|
22758
|
+
else this.save.course(owner);
|
|
22759
|
+
}
|
|
22760
|
+
this.unsaved.clear();
|
|
22761
|
+
}
|
|
22762
|
+
// ── The writes, phase by phase ──
|
|
22763
|
+
/** Courses, then lessons, that the files take off the community: first, before anything else changes. */
|
|
22764
|
+
unpublishWrites() {
|
|
22765
|
+
const writes = [];
|
|
22766
|
+
for (const course of this.plan.courses) {
|
|
22767
|
+
const { server, folder } = course;
|
|
22768
|
+
if (server?.state === "local" && server.server.published && !folder.published) {
|
|
22769
|
+
writes.push({ label: `course ${folder.slug} (unpublish)`, requests: 1, apply: () => this.updateCourse(folder, false) });
|
|
22770
|
+
}
|
|
22771
|
+
}
|
|
22772
|
+
for (const lesson of this.lessons()) {
|
|
22773
|
+
const { server, lesson: file, course } = lesson;
|
|
22774
|
+
if (server?.state === "local" && server.server.published && !file.published) {
|
|
22775
|
+
writes.push({ label: `lesson ${course.slug}/${file.slug} (unpublish)`, requests: 1, apply: () => lessonWrite(course, file, () => this.updateLesson(course, file, true)) });
|
|
22776
|
+
}
|
|
22777
|
+
}
|
|
22778
|
+
return writes;
|
|
22779
|
+
}
|
|
22780
|
+
/** New courses, as drafts; and the courses whose content changed, but for their publishing. */
|
|
22781
|
+
courseWrites() {
|
|
22782
|
+
return this.plan.courses.flatMap((course) => {
|
|
22783
|
+
const { server, folder } = course;
|
|
22784
|
+
if (!server) return [{ label: `course ${folder.slug} (create)`, requests: 1, apply: () => this.createCourse(folder) }];
|
|
22785
|
+
if (server.state !== "local" || server.server.published !== folder.published) return [];
|
|
22786
|
+
return [{ label: `course ${folder.slug}`, requests: 1, apply: () => this.updateCourse(folder, false) }];
|
|
22787
|
+
});
|
|
22788
|
+
}
|
|
22789
|
+
moduleWrites() {
|
|
22790
|
+
return this.plan.courses.flatMap((course) => course.modules.flatMap((module) => {
|
|
22791
|
+
const at = `${course.folder.slug}/${module.entry.key}`;
|
|
22792
|
+
if (!module.server) return [{ label: `module ${at} (create)`, requests: 1, apply: () => this.createModule(course.folder, module.entry) }];
|
|
22793
|
+
if (module.server.state !== "local") return [];
|
|
22794
|
+
return [{ label: `module ${at}`, requests: 1, apply: () => this.updateModule(course.folder, module.entry) }];
|
|
22795
|
+
}));
|
|
22796
|
+
}
|
|
22797
|
+
lessonWrites() {
|
|
22798
|
+
return this.lessons().flatMap((lesson) => {
|
|
22799
|
+
const { server, course, module, lesson: file } = lesson;
|
|
22800
|
+
const at = `${course.slug}/${file.slug}`;
|
|
22801
|
+
if (!server) return [{ label: `lesson ${at} (create)`, requests: 1, apply: () => lessonWrite(course, file, () => this.createLesson(course, module, file)) }];
|
|
22802
|
+
if (server.state !== "local") return [];
|
|
22803
|
+
const unpublished = server.server.published && !file.published;
|
|
22804
|
+
if (unpublished && !fileLesson(this.files.rooms, file).room_id?.startsWith(PENDING_ROOM)) return [];
|
|
22805
|
+
return [{ label: `lesson ${at}`, requests: 1, apply: () => lessonWrite(course, file, () => this.updateLesson(course, file, false)) }];
|
|
22806
|
+
});
|
|
22807
|
+
}
|
|
22808
|
+
/** How many position writes the plan predicts, every create landing as a new entity. */
|
|
22809
|
+
get plannedPlaces() {
|
|
22810
|
+
return this.predicted.moves.length;
|
|
22811
|
+
}
|
|
22812
|
+
/** The position writes, from the server's orders as the creates left them: when the phase is reached. */
|
|
22813
|
+
placeWrites() {
|
|
22814
|
+
return computePlaces(this.files, this.sim.clone(), realIds, this.placements).moves.map((move) => ({
|
|
22815
|
+
label: move.kind === "lesson" && this.sim.at.get(move.id) !== move.moduleId ? `lesson ${this.pathOf(move.id)} \u2192 module ${this.pathOf(move.moduleId)}` : `place of ${move.kind} ${this.pathOf(move.id)}`,
|
|
22816
|
+
requests: 1,
|
|
22817
|
+
apply: () => this.place(move)
|
|
22818
|
+
}));
|
|
22819
|
+
}
|
|
22820
|
+
/** Courses the files publish: last, once their lessons are in place. */
|
|
22821
|
+
publishWrites() {
|
|
22822
|
+
return this.plan.courses.flatMap((course) => {
|
|
22823
|
+
const { folder, server } = course;
|
|
22824
|
+
const now = folder.id === void 0 ? void 0 : this.courseNow.get(folder.id);
|
|
22825
|
+
if (!folder.published || now?.published || server !== null && server.state !== "local") return [];
|
|
22826
|
+
return [{ label: `course ${folder.slug} (publish)`, requests: 1, apply: () => this.updateCourse(folder, true) }];
|
|
22827
|
+
});
|
|
22828
|
+
}
|
|
22829
|
+
pathOf(id) {
|
|
22830
|
+
for (const course of this.files.courses) {
|
|
22831
|
+
if (course.id === id) return course.slug;
|
|
22832
|
+
for (const module of course.modules) {
|
|
22833
|
+
if (module.id === id) return `${course.slug}/${module.key}`;
|
|
22834
|
+
for (const lesson of module.lessons) if (lesson.id === id) return `${course.slug}/${lesson.slug}`;
|
|
22835
|
+
}
|
|
22836
|
+
}
|
|
22837
|
+
return this.name(id);
|
|
22838
|
+
}
|
|
22839
|
+
// ── Each write ──
|
|
22840
|
+
async createCourse(folder) {
|
|
22841
|
+
const made = await this.remote.createCourse({
|
|
22842
|
+
title: folder.title,
|
|
22843
|
+
description: folder.description,
|
|
22844
|
+
client_id: await createClientId(this.communityId, "course", folder.slug)
|
|
22845
|
+
});
|
|
22846
|
+
const twin = this.files.courses.find((other) => other !== folder && other.id === made.course_id);
|
|
22847
|
+
if (twin) {
|
|
22848
|
+
throw expected(
|
|
22849
|
+
`the name ${folder.slug} already names course ${made.course_id}, which your files also list as ${twin.slug}: a course folder's name keeps naming its course. Remove one of the two from ${COMMUNITY_FILE}'s courses.`
|
|
22850
|
+
);
|
|
22851
|
+
}
|
|
22852
|
+
const existed = this.sim.modules.get(made.course_id);
|
|
22853
|
+
folder.id = made.course_id;
|
|
22854
|
+
folder.rev = serverCourse(made).rev;
|
|
22855
|
+
this.courseNow.set(made.course_id, made);
|
|
22856
|
+
if (!existed) {
|
|
22857
|
+
append(this.sim.courses, made.course_id);
|
|
22858
|
+
this.sim.modules.set(made.course_id, freshOrder());
|
|
22859
|
+
}
|
|
22860
|
+
this.unsaved.add(folder);
|
|
22861
|
+
this.settle();
|
|
22862
|
+
await this.updateCourse(folder, false);
|
|
22863
|
+
}
|
|
22864
|
+
async updateCourse(folder, publish) {
|
|
22865
|
+
const now = this.courseNow.get(folder.id);
|
|
22866
|
+
const body = coursePatch(folder, now, publish);
|
|
22867
|
+
if (!body) return;
|
|
22868
|
+
const answer = await this.remote.updateCourse(folder.id, body);
|
|
22869
|
+
takeCourse(folder, answer, new Set(Object.keys(body)));
|
|
22870
|
+
folder.rev = serverCourse(answer).rev;
|
|
22871
|
+
this.courseNow.set(answer.course_id, answer);
|
|
22872
|
+
this.save.course(folder);
|
|
22873
|
+
}
|
|
22874
|
+
async createModule(course, entry) {
|
|
22875
|
+
const courseId = course.id;
|
|
22876
|
+
const made = await this.remote.createModule(courseId, {
|
|
22877
|
+
title: entry.title,
|
|
22878
|
+
client_id: await createClientId(this.communityId, "module", `${course.slug}/${entry.key}`)
|
|
22879
|
+
});
|
|
22880
|
+
if (made.course_id !== courseId) {
|
|
22881
|
+
throw expected(
|
|
22882
|
+
`the key ${entry.key} already names module ${show(made.title)} (${made.module_id}) of ${courseName(this.files, this.plan.server, made.course_id)}: a key keeps naming its module, and a module does not move between courses. Give the new module another key.`
|
|
22883
|
+
);
|
|
22884
|
+
}
|
|
22885
|
+
const twin = this.files.courses.flatMap((c) => c.modules.map((m) => ({ c, m }))).find(({ m }) => m !== entry && m.id === made.module_id);
|
|
22886
|
+
if (twin) {
|
|
22887
|
+
throw expected(`the key ${entry.key} already names module ${made.module_id}, which your files also list as ${twin.c.slug}/${twin.m.key}: remove one of the two entries.`);
|
|
22888
|
+
}
|
|
22889
|
+
const existed = this.sim.lessons.get(made.module_id);
|
|
22890
|
+
entry.id = made.module_id;
|
|
22891
|
+
entry.rev = serverModule(made).rev;
|
|
22892
|
+
this.moduleNow.set(made.module_id, made);
|
|
22893
|
+
if (!existed) {
|
|
22894
|
+
append(this.sim.modules.get(courseId), made.module_id);
|
|
22895
|
+
this.sim.lessons.set(made.module_id, freshOrder());
|
|
22896
|
+
}
|
|
22897
|
+
this.unsaved.add(course);
|
|
22898
|
+
this.settle();
|
|
22899
|
+
await this.updateModule(course, entry);
|
|
22900
|
+
}
|
|
22901
|
+
async updateModule(course, entry) {
|
|
22902
|
+
const now = this.moduleNow.get(entry.id);
|
|
22903
|
+
if (moduleContent(entry).title === moduleContent(now).title) return;
|
|
22904
|
+
const answer = await this.remote.updateModule(course.id, entry.id, { title: entry.title });
|
|
22905
|
+
if (moduleContent(answer).title !== moduleContent(entry).title) entry.title = answer.title;
|
|
22906
|
+
entry.rev = serverModule(answer).rev;
|
|
22907
|
+
this.moduleNow.set(answer.module_id, answer);
|
|
22908
|
+
this.save.course(course);
|
|
22909
|
+
}
|
|
22910
|
+
async createLesson(course, module, lesson) {
|
|
22911
|
+
const courseId = course.id;
|
|
22912
|
+
const roomId = fileLesson(this.files.rooms, lesson).room_id;
|
|
22913
|
+
if (roomId?.startsWith(PENDING_ROOM)) throw new Error(`createLesson: room ${lesson.room} has no id yet`);
|
|
22914
|
+
const made = await this.remote.createLesson(courseId, {
|
|
22915
|
+
module_id: module.id,
|
|
22916
|
+
title: lesson.title,
|
|
22917
|
+
...lesson.video_url === void 0 ? {} : { video_url: lesson.video_url },
|
|
22918
|
+
body: lesson.body,
|
|
22919
|
+
links: lesson.links,
|
|
22920
|
+
...roomId === null ? {} : { room_id: roomId },
|
|
22921
|
+
requires_paid_plan: lesson.requires_paid_plan,
|
|
22922
|
+
published: lesson.published,
|
|
22923
|
+
client_id: await createClientId(this.communityId, "lesson", `${course.slug}/${lesson.slug}`)
|
|
22924
|
+
});
|
|
22925
|
+
if (made.course_id !== courseId) {
|
|
22926
|
+
throw expected(
|
|
22927
|
+
`the file name ${course.slug}/${lesson.slug} already names lesson ${show(made.title)} (${made.lesson_id}) of ${courseName(this.files, this.plan.server, made.course_id)}: a lesson's file name keeps naming its lesson, and a lesson does not move between courses. Give the new lesson another file name.`
|
|
22928
|
+
);
|
|
22929
|
+
}
|
|
22930
|
+
const twin = this.lessons().find((other) => other.lesson !== lesson && other.lesson.id === made.lesson_id);
|
|
22931
|
+
if (twin) {
|
|
22932
|
+
throw expected(`the file name ${lesson.slug} already names lesson ${made.lesson_id}, which your files also list as ${twin.course.slug}/${twin.lesson.slug}: remove one of the two.`);
|
|
22933
|
+
}
|
|
22934
|
+
const existed = this.sim.at.has(made.lesson_id);
|
|
22935
|
+
lesson.id = made.lesson_id;
|
|
22936
|
+
lesson.rev = serverLesson(made).rev;
|
|
22937
|
+
this.lessonNow.set(made.lesson_id, made);
|
|
22938
|
+
if (!existed) {
|
|
22939
|
+
append(this.sim.lessons.get(made.module_id), made.lesson_id);
|
|
22940
|
+
this.sim.at.set(made.lesson_id, made.module_id);
|
|
22941
|
+
}
|
|
22942
|
+
this.save.lesson(course, lesson);
|
|
22943
|
+
this.settle();
|
|
22944
|
+
await this.updateLesson(course, lesson, false);
|
|
22945
|
+
}
|
|
22946
|
+
async updateLesson(course, lesson, waitForRooms) {
|
|
22947
|
+
const now = this.lessonNow.get(lesson.id);
|
|
22948
|
+
const body = lessonPatch(this.files.rooms, lesson, now, waitForRooms);
|
|
22949
|
+
if (!body) return;
|
|
22950
|
+
const answer = await this.remote.updateLesson(course.id, lesson.id, body);
|
|
22951
|
+
takeLesson(this.files.rooms, lesson, answer, new Set(Object.keys(body)));
|
|
22952
|
+
lesson.rev = serverLesson(answer).rev;
|
|
22953
|
+
this.lessonNow.set(answer.lesson_id, answer);
|
|
22954
|
+
this.save.lesson(course, lesson);
|
|
22955
|
+
}
|
|
22956
|
+
/**
|
|
22957
|
+
* A position write. One whose answer is lost may have moved the child or not: the server is read
|
|
22958
|
+
* again, and when the child stands where the write put it, the write is recorded as if answered —
|
|
22959
|
+
* the orders it changed, and no other — before the failure stands. A refusal is not read back.
|
|
22960
|
+
*/
|
|
22961
|
+
async place(move) {
|
|
22962
|
+
try {
|
|
22963
|
+
await this.sendPlace(move);
|
|
22964
|
+
} catch (err) {
|
|
22965
|
+
if (outcomeUnknown(err)) await this.recordIfLanded(move).catch(() => void 0);
|
|
22966
|
+
throw err;
|
|
22967
|
+
}
|
|
22968
|
+
}
|
|
22969
|
+
async sendPlace(move) {
|
|
22970
|
+
if (move.kind === "course") {
|
|
22971
|
+
const answer = await this.remote.updateCourse(move.id, { position: move.position });
|
|
22972
|
+
this.placedCourse(answer, answer.position);
|
|
22973
|
+
} else if (move.kind === "module") {
|
|
22974
|
+
const answer = await this.remote.updateModule(move.courseId, move.id, { position: move.position });
|
|
22975
|
+
this.placedModule(move.courseId, answer, answer.position);
|
|
22976
|
+
} else {
|
|
22977
|
+
const from = this.sim.at.get(move.id);
|
|
22978
|
+
const body = from === move.moduleId ? { position: move.position } : { module_id: move.moduleId, position: move.position };
|
|
22979
|
+
const answer = await this.remote.updateLesson(move.courseId, move.id, body);
|
|
22980
|
+
this.placedLesson(answer, answer.module_id, answer.position);
|
|
22981
|
+
}
|
|
22982
|
+
}
|
|
22983
|
+
/** A position write whose answer was lost, recorded when a read finds its child where it put it. */
|
|
22984
|
+
async recordIfLanded(move) {
|
|
22985
|
+
const wanted = (order) => {
|
|
22986
|
+
const ids = [...order.server];
|
|
22987
|
+
placeAt(ids, move.id, move.position);
|
|
22988
|
+
return ids.indexOf(move.id);
|
|
22989
|
+
};
|
|
22990
|
+
if (move.kind === "course") {
|
|
22991
|
+
const courses = await this.remote.listCourses();
|
|
22992
|
+
const at2 = courses.findIndex((course2) => course2.course_id === move.id);
|
|
22993
|
+
if (at2 >= 0 && at2 === wanted(this.sim.courses)) this.placedCourse(courses[at2], at2);
|
|
22994
|
+
return;
|
|
22995
|
+
}
|
|
22996
|
+
const course = await this.remote.exportCourse(move.courseId);
|
|
22997
|
+
if (move.kind === "module") {
|
|
22998
|
+
const at2 = course.modules.findIndex((module) => module.module_id === move.id);
|
|
22999
|
+
if (at2 >= 0 && at2 === wanted(this.sim.modules.get(move.courseId))) this.placedModule(move.courseId, course.modules[at2], at2);
|
|
23000
|
+
return;
|
|
23001
|
+
}
|
|
23002
|
+
const lessons = course.modules.find((module) => module.module_id === move.moduleId)?.lessons ?? [];
|
|
23003
|
+
const at = lessons.findIndex((lesson) => lesson.lesson_id === move.id);
|
|
23004
|
+
if (at >= 0 && at === wanted(this.sim.lessons.get(move.moduleId))) this.placedLesson(lessons[at], move.moduleId, at);
|
|
23005
|
+
}
|
|
23006
|
+
placedCourse(now, position) {
|
|
23007
|
+
shift(this.sim.courses, now.course_id, position);
|
|
23008
|
+
this.courseNow.set(now.course_id, now);
|
|
23009
|
+
this.settle();
|
|
23010
|
+
}
|
|
23011
|
+
placedModule(courseId, now, position) {
|
|
23012
|
+
shift(this.sim.modules.get(courseId), now.module_id, position);
|
|
23013
|
+
this.moduleNow.set(now.module_id, now);
|
|
23014
|
+
this.settle();
|
|
23015
|
+
}
|
|
23016
|
+
placedLesson(now, moduleId, position) {
|
|
23017
|
+
const from = this.sim.at.get(now.lesson_id);
|
|
23018
|
+
if (moduleId !== from) leave(this.sim.lessons.get(from), now.lesson_id);
|
|
23019
|
+
shift(this.sim.lessons.get(moduleId), now.lesson_id, position);
|
|
23020
|
+
this.sim.at.set(now.lesson_id, moduleId);
|
|
23021
|
+
this.lessonNow.set(now.lesson_id, now);
|
|
23022
|
+
this.settle();
|
|
23023
|
+
}
|
|
23024
|
+
/**
|
|
23025
|
+
* After a write that changed a parent's order (or a refresh), each such parent's synced list is
|
|
23026
|
+
* recorded and the files keeping a changed one are saved. A synced list is the server's order as
|
|
23027
|
+
* its own placement leaves it — less a lesson the server moved in since, and with one it moved
|
|
23028
|
+
* out, which the files have not taken: their base stays the module the files last synced them in
|
|
23029
|
+
* — or, for a parent whose order changed on the server only, the files' synced list as push left
|
|
23030
|
+
* it (`OrderSim.pinned`).
|
|
23031
|
+
*/
|
|
23032
|
+
settle() {
|
|
23033
|
+
const record = (order, current, base, set, owner) => {
|
|
23034
|
+
if (!order?.touched || order.conflict) return;
|
|
23035
|
+
order.touched = false;
|
|
23036
|
+
const ids = order.pinned ?? base(order, current ?? []);
|
|
23037
|
+
if (current && sameOrder(current, ids)) return;
|
|
23038
|
+
set([...ids]);
|
|
23039
|
+
this.unsaved.add(owner);
|
|
23040
|
+
};
|
|
23041
|
+
const asIs = (order) => order.server;
|
|
23042
|
+
record(this.sim.courses, this.files.synced_courses, asIs, (ids) => {
|
|
23043
|
+
this.files.synced_courses = ids;
|
|
23044
|
+
}, null);
|
|
23045
|
+
for (const course of this.files.courses) {
|
|
23046
|
+
const modules = course.id === void 0 ? void 0 : this.sim.modules.get(course.id);
|
|
23047
|
+
if (!modules) continue;
|
|
23048
|
+
const synced2 = () => course.synced ??= { lessons: {} };
|
|
23049
|
+
record(modules, course.synced?.modules, asIs, (ids) => {
|
|
23050
|
+
synced2().modules = ids;
|
|
23051
|
+
}, course);
|
|
23052
|
+
for (const moduleId of modules.server) {
|
|
23053
|
+
record(this.sim.lessons.get(moduleId), course.synced?.lessons[moduleId], (order, current) => this.lessonsBase(moduleId, order.server, current), (ids) => {
|
|
23054
|
+
synced2().lessons[moduleId] = ids;
|
|
23055
|
+
}, course);
|
|
23056
|
+
}
|
|
23057
|
+
}
|
|
23058
|
+
this.flush();
|
|
23059
|
+
}
|
|
23060
|
+
/**
|
|
23061
|
+
* A module's synced lessons: the server's, less those it moved in and with those it moved out, the
|
|
23062
|
+
* files not having taken either — each one moved out back in its place in the synced list as it
|
|
23063
|
+
* stood (`current`).
|
|
23064
|
+
*/
|
|
23065
|
+
lessonsBase(moduleId, server, current) {
|
|
23066
|
+
const movedIn = this.movedIn.get(moduleId);
|
|
23067
|
+
const ids = server.filter((id) => !movedIn?.has(id));
|
|
23068
|
+
for (const id of this.movedOut.get(moduleId) ?? []) if (!ids.includes(id)) insertAfter(ids, id, current);
|
|
23069
|
+
return ids;
|
|
23070
|
+
}
|
|
23071
|
+
};
|
|
23072
|
+
synced = (state, takes) => state !== "conflict" || takes(state);
|
|
23073
|
+
}
|
|
23074
|
+
});
|
|
23075
|
+
|
|
21079
23076
|
// src/communities/sync.ts
|
|
21080
23077
|
var sync_exports2 = {};
|
|
21081
23078
|
__export(sync_exports2, {
|
|
21082
23079
|
pullCommunity: () => pullCommunity,
|
|
21083
23080
|
pushCommunity: () => pushCommunity
|
|
21084
23081
|
});
|
|
21085
|
-
import * as
|
|
21086
|
-
import * as
|
|
23082
|
+
import * as fs19 from "fs";
|
|
23083
|
+
import * as path27 from "path";
|
|
21087
23084
|
function parseArgs5(args2) {
|
|
21088
23085
|
return {
|
|
21089
23086
|
autoConfirm: args2.includes("--yes") || args2.includes("-y"),
|
|
@@ -21144,11 +23141,22 @@ function planSync(files, server) {
|
|
|
21144
23141
|
},
|
|
21145
23142
|
rooms,
|
|
21146
23143
|
untracked: server.rooms.filter((room) => !tracked.has(room.room_id)),
|
|
21147
|
-
unknownIds
|
|
23144
|
+
unknownIds,
|
|
23145
|
+
courses: planCourses(files, server.courses)
|
|
21148
23146
|
};
|
|
21149
23147
|
}
|
|
21150
23148
|
function unknownIdProblems(plan) {
|
|
21151
|
-
return plan.unknownIds.map((entry) => `room ${entry.key}: ${entry.id} is not a room of this community`);
|
|
23149
|
+
return [...plan.unknownIds.map((entry) => `room ${entry.key}: ${entry.id} is not a room of this community`), ...plan.courses.problems];
|
|
23150
|
+
}
|
|
23151
|
+
function warnUnlisted(files) {
|
|
23152
|
+
for (const folder of files.unlistedCourses) {
|
|
23153
|
+
console.warn(`Skipped: ${unlistedPath(folder.name)} \u2014 ${COMMUNITY_FILE}'s courses does not list it.`);
|
|
23154
|
+
}
|
|
23155
|
+
for (const course of files.courses) {
|
|
23156
|
+
for (const file of course.unlisted) {
|
|
23157
|
+
console.warn(`Skipped: ${unlistedPath(file.name, course)} \u2014 no module of the course lists it.`);
|
|
23158
|
+
}
|
|
23159
|
+
}
|
|
21152
23160
|
}
|
|
21153
23161
|
function settingsChanges(from, to) {
|
|
21154
23162
|
const out = [];
|
|
@@ -21157,31 +23165,6 @@ function settingsChanges(from, to) {
|
|
|
21157
23165
|
if (from.rules !== to.rules) out.push("rules (rules.md)");
|
|
21158
23166
|
return out;
|
|
21159
23167
|
}
|
|
21160
|
-
function lineDiff(from, to) {
|
|
21161
|
-
const a = from ? sanitizeTerminalText(from).split("\n") : [];
|
|
21162
|
-
const b = to ? sanitizeTerminalText(to).split("\n") : [];
|
|
21163
|
-
if (a.length * b.length > 25e4) return [...a.map((l) => `- ${l}`), ...b.map((l) => `+ ${l}`)];
|
|
21164
|
-
const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
21165
|
-
for (let i2 = a.length - 1; i2 >= 0; i2--) {
|
|
21166
|
-
for (let j2 = b.length - 1; j2 >= 0; j2--) {
|
|
21167
|
-
lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
|
|
21168
|
-
}
|
|
21169
|
-
}
|
|
21170
|
-
const out = [];
|
|
21171
|
-
let i = 0;
|
|
21172
|
-
let j = 0;
|
|
21173
|
-
while (i < a.length || j < b.length) {
|
|
21174
|
-
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
21175
|
-
i++;
|
|
21176
|
-
j++;
|
|
21177
|
-
} else if (j >= b.length || i < a.length && lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
21178
|
-
out.push(`- ${a[i++]}`);
|
|
21179
|
-
} else {
|
|
21180
|
-
out.push(`+ ${b[j++]}`);
|
|
21181
|
-
}
|
|
21182
|
-
}
|
|
21183
|
-
return out;
|
|
21184
|
-
}
|
|
21185
23168
|
function roomChanges(from, to) {
|
|
21186
23169
|
const out = [];
|
|
21187
23170
|
if (from.name !== to.name) out.push(`name ${show(from.name)} \u2192 ${show(to.name)}`);
|
|
@@ -21191,19 +23174,6 @@ function roomChanges(from, to) {
|
|
|
21191
23174
|
function joinLinkChange(from, to) {
|
|
21192
23175
|
return to ? `${onOff(from)} \u2192 on: a new link \u2014 every link shared before stays dead` : `${onOff(from)} \u2192 off: every link shared before stops working`;
|
|
21193
23176
|
}
|
|
21194
|
-
function printSection(title, lines) {
|
|
21195
|
-
if (lines.length === 0) return;
|
|
21196
|
-
console.log(`
|
|
21197
|
-
${title}`);
|
|
21198
|
-
for (const line of lines) console.log(` ${line}`);
|
|
21199
|
-
}
|
|
21200
|
-
function printListing(listing) {
|
|
21201
|
-
printSection("Changes:", listing.changes);
|
|
21202
|
-
printSection("Changed on the server since your last pull \u2014 left as is; pull to take it:", listing.serverChanged);
|
|
21203
|
-
printSection("Kept \u2014 edited in your files, not pushed yet:", listing.kept);
|
|
21204
|
-
printSection("Not in your files \u2014 left as is:", listing.leftAsIs);
|
|
21205
|
-
printSection("Conflicts \u2014 changed in your files and on the server since your last pull:", listing.conflicts);
|
|
21206
|
-
}
|
|
21207
23177
|
function pushProblems(files, server, plan) {
|
|
21208
23178
|
const problems2 = unknownIdProblems(plan);
|
|
21209
23179
|
if (files.name !== server.community.name) {
|
|
@@ -21215,12 +23185,13 @@ function pushProblems(files, server, plan) {
|
|
|
21215
23185
|
}
|
|
21216
23186
|
}
|
|
21217
23187
|
const creates = plan.rooms.filter((room) => room.kind === "create").length;
|
|
21218
|
-
const
|
|
21219
|
-
if (server.rooms.length +
|
|
23188
|
+
const certainlyNew2 = Math.max(0, creates - plan.untracked.length);
|
|
23189
|
+
if (server.rooms.length + certainlyNew2 > COMMUNITY_ROOMS_MAX) {
|
|
21220
23190
|
problems2.push(
|
|
21221
23191
|
`rooms: a community holds at most ${COMMUNITY_ROOMS_MAX} rooms, archived ones included; it has ${server.rooms.length}, and the files create ${creates}`
|
|
21222
23192
|
);
|
|
21223
23193
|
}
|
|
23194
|
+
problems2.push(...courseCapProblems(files, plan.courses));
|
|
21224
23195
|
return problems2;
|
|
21225
23196
|
}
|
|
21226
23197
|
function settingsPatch(files, server) {
|
|
@@ -21260,7 +23231,18 @@ function takeJoinLink(files, enabled) {
|
|
|
21260
23231
|
files.join_link_enabled = enabled;
|
|
21261
23232
|
files.join_link_rev = joinLinkRev(files.community_id, { enabled });
|
|
21262
23233
|
}
|
|
21263
|
-
function
|
|
23234
|
+
function pushPhases(files, plan, remote, save, courses) {
|
|
23235
|
+
return [
|
|
23236
|
+
courses.unpublishWrites(),
|
|
23237
|
+
communityWrites(files, plan, remote, save),
|
|
23238
|
+
courses.courseWrites(),
|
|
23239
|
+
courses.moduleWrites(),
|
|
23240
|
+
courses.lessonWrites(),
|
|
23241
|
+
{ planned: courses.plannedPlaces, later: () => courses.placeWrites() },
|
|
23242
|
+
{ planned: courses.publishWrites().length, later: () => courses.publishWrites() }
|
|
23243
|
+
];
|
|
23244
|
+
}
|
|
23245
|
+
function communityWrites(files, plan, remote, save) {
|
|
21264
23246
|
const writes = [];
|
|
21265
23247
|
if (plan.settings.state === "local") {
|
|
21266
23248
|
const body = settingsPatch(files, plan.settings.server);
|
|
@@ -21350,8 +23332,8 @@ function refreshConverged(files, plan) {
|
|
|
21350
23332
|
}
|
|
21351
23333
|
return refreshed;
|
|
21352
23334
|
}
|
|
21353
|
-
function pushListing(plan) {
|
|
21354
|
-
const listing =
|
|
23335
|
+
function pushListing(plan, courses) {
|
|
23336
|
+
const listing = emptyListing();
|
|
21355
23337
|
const { settings, joinLink } = plan;
|
|
21356
23338
|
if (settings.state === "local") {
|
|
21357
23339
|
listing.changes.push(`~ settings: ${settingsChanges(settings.server, settings.file).join("; ")}`);
|
|
@@ -21376,6 +23358,7 @@ function pushListing(plan) {
|
|
|
21376
23358
|
listing.serverChanged = named("remote");
|
|
21377
23359
|
listing.conflicts = named("conflict");
|
|
21378
23360
|
listing.leftAsIs = plan.untracked.map((room) => `room ${show(room.name)} (${room.room_id})`);
|
|
23361
|
+
courses.list(listing);
|
|
21379
23362
|
return listing;
|
|
21380
23363
|
}
|
|
21381
23364
|
async function pushCommunity(gitRootOrNull, selector, args2) {
|
|
@@ -21394,37 +23377,39 @@ async function pushCommunity(gitRootOrNull, selector, args2) {
|
|
|
21394
23377
|
throw expected("Push changes the live community and asks first: pass -y when not running in a terminal, or --dry-run to only list the changes.");
|
|
21395
23378
|
}
|
|
21396
23379
|
const { organization_id: orgId } = requireRepoConfig(gitRoot);
|
|
21397
|
-
const label =
|
|
23380
|
+
const label = path27.basename(folder);
|
|
21398
23381
|
assertInScope("communities", files.community_id, label);
|
|
23382
|
+
warnUnlisted(files);
|
|
21399
23383
|
const remote = await openRemote(files.community_id);
|
|
21400
23384
|
const server = await remote.read(orgId);
|
|
21401
23385
|
const plan = planSync(files, server);
|
|
21402
|
-
console.log(`Community ${show(server.community.name)} (${server.community.community_id}) \u2014 ${
|
|
23386
|
+
console.log(`Community ${show(server.community.name)} (${server.community.community_id}) \u2014 ${path27.relative(gitRoot, folder)}/`);
|
|
21403
23387
|
const problems2 = pushProblems(files, server, plan);
|
|
21404
23388
|
if (problems2.length > 0) {
|
|
21405
23389
|
throw expected(`Nothing was pushed:
|
|
21406
23390
|
${problems2.map((p) => ` - ${p}`).join("\n")}`);
|
|
21407
23391
|
}
|
|
21408
|
-
const
|
|
23392
|
+
const { communitiesDir } = resolveLayout(gitRoot);
|
|
23393
|
+
const save = () => {
|
|
23394
|
+
writeCommunityFiles(communitiesDir, folder, files);
|
|
23395
|
+
};
|
|
23396
|
+
const courses = new CoursePush(files, plan.courses, remote, courseSaver(communitiesDir, folder, save));
|
|
23397
|
+
const listing = pushListing(plan, courses);
|
|
21409
23398
|
printListing(listing);
|
|
21410
23399
|
if (listing.conflicts.length > 0) {
|
|
21411
23400
|
throw expected(`
|
|
21412
23401
|
Nothing was pushed: ${listing.conflicts.length} conflict(s). ${resolveConflicts(folder)}`);
|
|
21413
23402
|
}
|
|
21414
|
-
const
|
|
21415
|
-
const
|
|
21416
|
-
|
|
21417
|
-
|
|
21418
|
-
const writes = pushWrites(files, plan, remote, save);
|
|
21419
|
-
if (writes.length === 0) {
|
|
21420
|
-
const refreshed = dryRun ? 0 : refreshConverged(files, plan);
|
|
23403
|
+
const phases = pushPhases(files, plan, remote, save, courses);
|
|
23404
|
+
const requests = phases.reduce((sum, phase) => sum + plannedRequests(phase), 0);
|
|
23405
|
+
if (requests === 0) {
|
|
23406
|
+
const refreshed = dryRun ? 0 : refreshConverged(files, plan) + courses.refreshConverged();
|
|
21421
23407
|
if (refreshed > 0) save();
|
|
21422
23408
|
console.log(`
|
|
21423
|
-
No changes to push${refreshed > 0 ? ` (${refreshed} rev(s) refreshed
|
|
23409
|
+
No changes to push${refreshed > 0 ? ` (${refreshed} rev(s) refreshed)` : ""}.`);
|
|
21424
23410
|
if (!dryRun) seedScopeIfEmpty("communities", files.community_id);
|
|
21425
23411
|
return;
|
|
21426
23412
|
}
|
|
21427
|
-
const requests = writes.reduce((sum, write) => sum + write.requests, 0);
|
|
21428
23413
|
console.log(
|
|
21429
23414
|
`
|
|
21430
23415
|
This push makes ${requests} request(s) to the live community \u2014 ${paceDuration(requests)} at ${COMMUNITY_REQUESTS_PER_MINUTE} a minute.`
|
|
@@ -21446,24 +23431,67 @@ This push makes ${requests} request(s) to the live community \u2014 ${paceDurati
|
|
|
21446
23431
|
}
|
|
21447
23432
|
}
|
|
21448
23433
|
refreshConverged(files, plan);
|
|
23434
|
+
courses.refreshConverged();
|
|
21449
23435
|
save();
|
|
21450
|
-
|
|
21451
|
-
|
|
21452
|
-
|
|
21453
|
-
|
|
21454
|
-
|
|
21455
|
-
|
|
21456
|
-
|
|
21457
|
-
)
|
|
23436
|
+
let total = phases.reduce((sum, phase) => sum + plannedCount(phase), 0);
|
|
23437
|
+
let done = 0;
|
|
23438
|
+
const stop = stopAfterWrite();
|
|
23439
|
+
try {
|
|
23440
|
+
for (const phase of phases) {
|
|
23441
|
+
const writes = Array.isArray(phase) ? phase : phase.later();
|
|
23442
|
+
total += writes.length - plannedCount(phase);
|
|
23443
|
+
for (const write of writes) {
|
|
23444
|
+
if (stop.requested()) {
|
|
23445
|
+
throw expected(
|
|
23446
|
+
`Push stopped on request after ${done} change(s). What it applied is on the community, and the files record it: push again to go on.`
|
|
23447
|
+
);
|
|
23448
|
+
}
|
|
23449
|
+
try {
|
|
23450
|
+
await write.apply();
|
|
23451
|
+
} catch (err) {
|
|
23452
|
+
throw expected(
|
|
23453
|
+
`Push stopped at ${write.label}: ${stopReason(err)}
|
|
23454
|
+
What it applied before stopping is on the community, and the files record it. Fix the cause and push again.`
|
|
23455
|
+
);
|
|
23456
|
+
}
|
|
23457
|
+
console.log(` [${++done}/${total}] ${write.label}`);
|
|
23458
|
+
}
|
|
21458
23459
|
}
|
|
21459
|
-
|
|
23460
|
+
} finally {
|
|
23461
|
+
stop.release();
|
|
21460
23462
|
}
|
|
21461
23463
|
console.log(
|
|
21462
23464
|
`
|
|
21463
|
-
Pushed ${
|
|
23465
|
+
Pushed ${done} change(s). Commit ${path27.relative(gitRoot, folder)}/: the files now record the ids and revs this push wrote.`
|
|
21464
23466
|
);
|
|
21465
23467
|
seedScopeIfEmpty("communities", files.community_id);
|
|
21466
23468
|
}
|
|
23469
|
+
function stopAfterWrite() {
|
|
23470
|
+
let asked = false;
|
|
23471
|
+
const onSignal = (signal) => {
|
|
23472
|
+
if (asked) process.exit(SIGNAL_EXIT_CODE[signal]);
|
|
23473
|
+
asked = true;
|
|
23474
|
+
console.error("\n Stopping after the write in progress, once the files record it. Press Ctrl-C again to leave at once.");
|
|
23475
|
+
};
|
|
23476
|
+
for (const signal of HANDLED_SIGNALS) process.on(signal, onSignal);
|
|
23477
|
+
return {
|
|
23478
|
+
requested: () => asked,
|
|
23479
|
+
release: () => {
|
|
23480
|
+
for (const signal of HANDLED_SIGNALS) process.off(signal, onSignal);
|
|
23481
|
+
}
|
|
23482
|
+
};
|
|
23483
|
+
}
|
|
23484
|
+
function courseSaver(root, folder, community) {
|
|
23485
|
+
return {
|
|
23486
|
+
community,
|
|
23487
|
+
course: (course) => {
|
|
23488
|
+
writeCourseYaml(root, folder, course);
|
|
23489
|
+
},
|
|
23490
|
+
lesson: (course, lesson) => {
|
|
23491
|
+
writeLessonFile(root, folder, course, lesson);
|
|
23492
|
+
}
|
|
23493
|
+
};
|
|
23494
|
+
}
|
|
21467
23495
|
function changedSince(before, after) {
|
|
21468
23496
|
const id = before.community.community_id;
|
|
21469
23497
|
const changed = [];
|
|
@@ -21475,6 +23503,7 @@ function changedSince(before, after) {
|
|
|
21475
23503
|
for (const room of after.rooms) {
|
|
21476
23504
|
if (revs.get(room.room_id) !== serverRoom(room).rev) changed.push(`room ${show(room.name)}`);
|
|
21477
23505
|
}
|
|
23506
|
+
changed.push(...coursesChangedSince(before.courses, after.courses));
|
|
21478
23507
|
return changed;
|
|
21479
23508
|
}
|
|
21480
23509
|
function stopReason(err) {
|
|
@@ -21498,15 +23527,18 @@ function filesFromServer(server) {
|
|
|
21498
23527
|
settings: { slow_mode_seconds: 0, courses_label: "" },
|
|
21499
23528
|
rules: "",
|
|
21500
23529
|
join_link_enabled: false,
|
|
21501
|
-
rooms: []
|
|
23530
|
+
rooms: [],
|
|
23531
|
+
courses: [],
|
|
23532
|
+
unlistedCourses: []
|
|
21502
23533
|
};
|
|
21503
23534
|
takeSettings(files, server.settings);
|
|
21504
23535
|
takeJoinLink(files, server.joinLink.enabled);
|
|
21505
23536
|
addUntracked(files, server.rooms);
|
|
23537
|
+
coursesFromServer(files, server.courses);
|
|
21506
23538
|
return files;
|
|
21507
23539
|
}
|
|
21508
23540
|
function pullMerge(files, server, plan, theirs) {
|
|
21509
|
-
const listing =
|
|
23541
|
+
const listing = emptyListing();
|
|
21510
23542
|
const takes = (state) => state === "remote" || state === "conflict" && theirs;
|
|
21511
23543
|
const note = (state, what) => {
|
|
21512
23544
|
if (state === "local") listing.kept.push(what);
|
|
@@ -21544,6 +23576,7 @@ function pullMerge(files, server, plan, theirs) {
|
|
|
21544
23576
|
}
|
|
21545
23577
|
refreshConverged(files, plan);
|
|
21546
23578
|
listing.changes.push(...addUntracked(files, plan.untracked));
|
|
23579
|
+
pullCourses(files, plan.courses, takes, note, listing);
|
|
21547
23580
|
return listing;
|
|
21548
23581
|
}
|
|
21549
23582
|
async function pullCommunity(gitRootOrNull, selector, args2) {
|
|
@@ -21559,26 +23592,28 @@ async function pullCommunity(gitRootOrNull, selector, args2) {
|
|
|
21559
23592
|
communityId = existing.files.community_id;
|
|
21560
23593
|
}
|
|
21561
23594
|
const { organization_id: orgId } = requireRepoConfig(gitRoot);
|
|
21562
|
-
assertInScope("communities", communityId, existing ?
|
|
23595
|
+
assertInScope("communities", communityId, existing ? path27.basename(existing.folder) : void 0);
|
|
21563
23596
|
const { communitiesDir } = resolveLayout(gitRoot);
|
|
21564
23597
|
const remote = await openRemote(communityId);
|
|
21565
23598
|
const server = await remote.read(orgId);
|
|
21566
23599
|
if (!existing) {
|
|
21567
23600
|
const taken = new Set(safeReaddir2(communitiesDir));
|
|
21568
|
-
const folder2 =
|
|
23601
|
+
const folder2 = path27.join(communitiesDir, localName(server.community.name, communityId, taken, "community"));
|
|
21569
23602
|
const files = filesFromServer(server);
|
|
21570
|
-
const where = `${
|
|
23603
|
+
const where = `${path27.relative(gitRoot, folder2)}/`;
|
|
23604
|
+
const counts = `${files.rooms.length} room(s), ${files.courses.length} course(s)`;
|
|
21571
23605
|
if (dryRun) {
|
|
21572
|
-
console.log(`Dry run \u2014 would write community ${show(server.community.name)} to ${where} with ${
|
|
23606
|
+
console.log(`Dry run \u2014 would write community ${show(server.community.name)} to ${where} with ${counts}.`);
|
|
21573
23607
|
return;
|
|
21574
23608
|
}
|
|
21575
23609
|
requireRealSubdirNoSymlink(gitRoot, communitiesDir, true);
|
|
21576
23610
|
writeCommunityFolder(communitiesDir, folder2, files);
|
|
21577
|
-
console.log(`Community ${show(server.community.name)} written to ${where} (${
|
|
23611
|
+
console.log(`Community ${show(server.community.name)} written to ${where} (${counts}). Commit it: its ids and revs are the base the next push compares against.`);
|
|
21578
23612
|
seedScopeIfEmpty("communities", communityId);
|
|
21579
23613
|
return;
|
|
21580
23614
|
}
|
|
21581
23615
|
const { folder, files: local } = existing;
|
|
23616
|
+
warnUnlisted(local);
|
|
21582
23617
|
const plan = planSync(local, server);
|
|
21583
23618
|
const problems2 = unknownIdProblems(plan);
|
|
21584
23619
|
if (problems2.length > 0) {
|
|
@@ -21586,7 +23621,7 @@ async function pullCommunity(gitRootOrNull, selector, args2) {
|
|
|
21586
23621
|
${problems2.map((p) => ` - ${p}`).join("\n")}`);
|
|
21587
23622
|
}
|
|
21588
23623
|
const listing = pullMerge(local, server, plan, theirs);
|
|
21589
|
-
console.log(`Community ${show(server.community.name)} (${communityId}) \u2014 ${
|
|
23624
|
+
console.log(`Community ${show(server.community.name)} (${communityId}) \u2014 ${path27.relative(gitRoot, folder)}/`);
|
|
21590
23625
|
printListing(listing);
|
|
21591
23626
|
if (dryRun) {
|
|
21592
23627
|
console.log("\nDry run \u2014 nothing was written.");
|
|
@@ -21601,16 +23636,16 @@ Wrote ${written.join(", ")}.` : "\nAlready up to date.");
|
|
|
21601
23636
|
}
|
|
21602
23637
|
}
|
|
21603
23638
|
function resolveConflicts(folder) {
|
|
21604
|
-
return `To resolve, take the server's side \u2014 wayai pull ${WAYAI_LAYOUT.communitiesSubdir}/${
|
|
23639
|
+
return `To resolve, take the server's side \u2014 wayai pull ${WAYAI_LAYOUT.communitiesSubdir}/${path27.basename(folder)} --theirs \u2014 then make again any edit of yours to keep, and push.`;
|
|
21605
23640
|
}
|
|
21606
23641
|
function safeReaddir2(dir) {
|
|
21607
23642
|
try {
|
|
21608
|
-
return
|
|
23643
|
+
return fs19.readdirSync(dir);
|
|
21609
23644
|
} catch {
|
|
21610
23645
|
return [];
|
|
21611
23646
|
}
|
|
21612
23647
|
}
|
|
21613
|
-
var
|
|
23648
|
+
var Remote, onOff, plannedCount, plannedRequests;
|
|
21614
23649
|
var init_sync2 = __esm({
|
|
21615
23650
|
"src/communities/sync.ts"() {
|
|
21616
23651
|
"use strict";
|
|
@@ -21626,11 +23661,14 @@ var init_sync2 = __esm({
|
|
|
21626
23661
|
init_worktree_scope();
|
|
21627
23662
|
init_community_workspace();
|
|
21628
23663
|
init_helpers();
|
|
23664
|
+
init_eval_session_control();
|
|
21629
23665
|
init_api2();
|
|
21630
23666
|
init_pacer();
|
|
21631
23667
|
init_files();
|
|
23668
|
+
init_course_files();
|
|
23669
|
+
init_courses();
|
|
23670
|
+
init_listing();
|
|
21632
23671
|
init_model();
|
|
21633
|
-
show = (text) => JSON.stringify(sanitizeTerminalText(text));
|
|
21634
23672
|
Remote = class {
|
|
21635
23673
|
constructor(api, pacer, communityId) {
|
|
21636
23674
|
this.api = api;
|
|
@@ -21641,8 +23679,9 @@ var init_sync2 = __esm({
|
|
|
21641
23679
|
return (await callCommunities(this.pacer.run(request), notFound)).data;
|
|
21642
23680
|
}
|
|
21643
23681
|
/**
|
|
21644
|
-
*
|
|
21645
|
-
*
|
|
23682
|
+
* gh #5003 Decision 8's bounded read: the org's list, settings, rooms, the join link and the
|
|
23683
|
+
* courses, then each course whole — one request per course. The org's list both binds the
|
|
23684
|
+
* community to the workspace's organization and names it.
|
|
21646
23685
|
*/
|
|
21647
23686
|
async read(orgId) {
|
|
21648
23687
|
const communities = await this.send(() => this.api.listOrgCommunities(orgId), orgNotFound(orgId));
|
|
@@ -21652,12 +23691,14 @@ var init_sync2 = __esm({
|
|
|
21652
23691
|
`Community ${this.communityId} is not one of the communities of organization ${orgId}, the one ${WORKSPACE_MANIFEST_LABEL} names. A community is pulled and pushed from a workspace bound to the organization that owns it.`
|
|
21653
23692
|
);
|
|
21654
23693
|
}
|
|
21655
|
-
const [settings, rooms, joinLink] = await Promise.all([
|
|
23694
|
+
const [settings, rooms, joinLink, courses] = await Promise.all([
|
|
21656
23695
|
this.send(() => this.api.getCommunitySettings(this.communityId)),
|
|
21657
23696
|
this.send(() => this.api.listCommunityRooms(this.communityId)),
|
|
21658
|
-
this.send(() => this.api.getCommunityJoinLink(this.communityId))
|
|
23697
|
+
this.send(() => this.api.getCommunityJoinLink(this.communityId)),
|
|
23698
|
+
// Each course whole, as soon as the list names it.
|
|
23699
|
+
this.listCourses().then((list) => Promise.all(list.map((course) => this.exportCourse(course.course_id))))
|
|
21659
23700
|
]);
|
|
21660
|
-
return { community, settings, rooms, joinLink };
|
|
23701
|
+
return { community, settings, rooms, joinLink, courses: new ServerCourses(courses) };
|
|
21661
23702
|
}
|
|
21662
23703
|
updateSettings(body) {
|
|
21663
23704
|
return this.send(() => this.api.updateCommunitySettings(this.communityId, body));
|
|
@@ -21672,8 +23713,34 @@ var init_sync2 = __esm({
|
|
|
21672
23713
|
updateRoom(roomId, body) {
|
|
21673
23714
|
return this.send(() => this.api.updateCommunityRoom(this.communityId, roomId, body));
|
|
21674
23715
|
}
|
|
23716
|
+
createCourse(body) {
|
|
23717
|
+
return this.send(() => this.api.createCommunityCourse(this.communityId, body));
|
|
23718
|
+
}
|
|
23719
|
+
updateCourse(courseId, body) {
|
|
23720
|
+
return this.send(() => this.api.updateCommunityCourse(this.communityId, courseId, body));
|
|
23721
|
+
}
|
|
23722
|
+
createModule(courseId, body) {
|
|
23723
|
+
return this.send(() => this.api.createCommunityModule(this.communityId, courseId, body));
|
|
23724
|
+
}
|
|
23725
|
+
updateModule(courseId, moduleId, body) {
|
|
23726
|
+
return this.send(() => this.api.updateCommunityModule(this.communityId, courseId, moduleId, body));
|
|
23727
|
+
}
|
|
23728
|
+
createLesson(courseId, body) {
|
|
23729
|
+
return this.send(() => this.api.createCommunityLesson(this.communityId, courseId, body));
|
|
23730
|
+
}
|
|
23731
|
+
updateLesson(courseId, lessonId, body) {
|
|
23732
|
+
return this.send(() => this.api.updateCommunityLesson(this.communityId, courseId, lessonId, body));
|
|
23733
|
+
}
|
|
23734
|
+
listCourses() {
|
|
23735
|
+
return this.send(() => this.api.listCommunityCourseDrafts(this.communityId));
|
|
23736
|
+
}
|
|
23737
|
+
exportCourse(courseId) {
|
|
23738
|
+
return this.send(() => this.api.exportCommunityCourse(this.communityId, courseId));
|
|
23739
|
+
}
|
|
21675
23740
|
};
|
|
21676
23741
|
onOff = (enabled) => enabled ? "on" : "off";
|
|
23742
|
+
plannedCount = (phase) => Array.isArray(phase) ? phase.length : phase.planned;
|
|
23743
|
+
plannedRequests = (phase) => Array.isArray(phase) ? phase.reduce((n, write) => n + write.requests, 0) : phase.planned;
|
|
21677
23744
|
}
|
|
21678
23745
|
});
|
|
21679
23746
|
|
|
@@ -21692,9 +23759,9 @@ __export(push_exports, {
|
|
|
21692
23759
|
shouldWarnIgnoredPreviewLabel: () => shouldWarnIgnoredPreviewLabel,
|
|
21693
23760
|
syncAfterPush: () => syncAfterPush
|
|
21694
23761
|
});
|
|
21695
|
-
import * as
|
|
21696
|
-
import * as
|
|
21697
|
-
import * as
|
|
23762
|
+
import * as fs20 from "fs";
|
|
23763
|
+
import * as path28 from "path";
|
|
23764
|
+
import * as yaml14 from "js-yaml";
|
|
21698
23765
|
function parseArgs6(args2) {
|
|
21699
23766
|
let autoConfirm = false;
|
|
21700
23767
|
let label;
|
|
@@ -21799,14 +23866,14 @@ function printLocalFileChanges(delta) {
|
|
|
21799
23866
|
}
|
|
21800
23867
|
async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, remoteConfig) {
|
|
21801
23868
|
requireRealHubFolder(hubFolder, false);
|
|
21802
|
-
const agentsDir =
|
|
23869
|
+
const agentsDir = path28.join(hubFolder, "agents");
|
|
21803
23870
|
let agentsWithIds = [];
|
|
21804
|
-
if (
|
|
21805
|
-
const yamlFiles =
|
|
23871
|
+
if (fs20.existsSync(agentsDir)) {
|
|
23872
|
+
const yamlFiles = fs20.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml"));
|
|
21806
23873
|
for (const file of yamlFiles) {
|
|
21807
|
-
const content = readRealFileOrThrow(hubFolder,
|
|
23874
|
+
const content = readRealFileOrThrow(hubFolder, path28.join(agentsDir, file)) ?? "";
|
|
21808
23875
|
try {
|
|
21809
|
-
const agent =
|
|
23876
|
+
const agent = yaml14.load(content);
|
|
21810
23877
|
if (agent?.id && agent.name) {
|
|
21811
23878
|
agentsWithIds.push({ id: agent.id, name: agent.name });
|
|
21812
23879
|
}
|
|
@@ -21819,7 +23886,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
|
|
|
21819
23886
|
const yamlPath = resolveHubYamlPath(hubFolder);
|
|
21820
23887
|
if (!yamlPath) return;
|
|
21821
23888
|
const yamlContent = readRealFileOrThrow(hubFolder, yamlPath) ?? "";
|
|
21822
|
-
const config =
|
|
23889
|
+
const config = yaml14.load(yamlContent);
|
|
21823
23890
|
agentsWithIds = (config.agents || []).filter((a) => !!a.id && !!a.name);
|
|
21824
23891
|
}
|
|
21825
23892
|
if (agentsWithIds.length === 0) return;
|
|
@@ -21850,18 +23917,18 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
|
|
|
21850
23917
|
}
|
|
21851
23918
|
}
|
|
21852
23919
|
if (renames.length === 0) return;
|
|
21853
|
-
if (!
|
|
21854
|
-
for (const file of
|
|
23920
|
+
if (!fs20.existsSync(agentsDir)) return;
|
|
23921
|
+
for (const file of fs20.readdirSync(agentsDir)) {
|
|
21855
23922
|
if (file.startsWith("__rename_temp_") && (file.endsWith(".md") || file.endsWith(".yaml"))) {
|
|
21856
23923
|
console.warn(` Warning: removing orphaned temp file agents/${file}`);
|
|
21857
|
-
|
|
23924
|
+
fs20.unlinkSync(path28.join(agentsDir, file));
|
|
21858
23925
|
}
|
|
21859
23926
|
}
|
|
21860
23927
|
const renameFileIfExists = (dir, oldName, newName) => {
|
|
21861
|
-
const oldPath =
|
|
21862
|
-
const newPath =
|
|
21863
|
-
if (!
|
|
21864
|
-
|
|
23928
|
+
const oldPath = path28.join(dir, oldName);
|
|
23929
|
+
const newPath = path28.join(dir, newName);
|
|
23930
|
+
if (!fs20.existsSync(oldPath)) return false;
|
|
23931
|
+
fs20.renameSync(oldPath, newPath);
|
|
21865
23932
|
return true;
|
|
21866
23933
|
};
|
|
21867
23934
|
const oldSlugs = new Set(renames.map((r) => r.oldSlug));
|
|
@@ -21894,9 +23961,9 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
|
|
|
21894
23961
|
}
|
|
21895
23962
|
} else {
|
|
21896
23963
|
for (const { oldSlug, newSlug } of renames) {
|
|
21897
|
-
const hasOldFile = extensions.some((ext) =>
|
|
23964
|
+
const hasOldFile = extensions.some((ext) => fs20.existsSync(path28.join(agentsDir, `${oldSlug}${ext}`)));
|
|
21898
23965
|
if (!hasOldFile) continue;
|
|
21899
|
-
const hasNewFile = extensions.some((ext) =>
|
|
23966
|
+
const hasNewFile = extensions.some((ext) => fs20.existsSync(path28.join(agentsDir, `${newSlug}${ext}`)));
|
|
21900
23967
|
if (hasNewFile) {
|
|
21901
23968
|
console.warn(` Warning: skipping rename agents/${oldSlug}.* \u2192 agents/${newSlug}.* (target already exists)`);
|
|
21902
23969
|
continue;
|
|
@@ -21923,7 +23990,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
|
|
|
21923
23990
|
);
|
|
21924
23991
|
if (updatedYaml !== mainYamlContent) {
|
|
21925
23992
|
writeFileNoFollow(hubFolder, mainYamlPath, Buffer.from(updatedYaml, "utf-8"));
|
|
21926
|
-
console.log(` Updated instructions paths in ${
|
|
23993
|
+
console.log(` Updated instructions paths in ${path28.basename(mainYamlPath)}`);
|
|
21927
23994
|
}
|
|
21928
23995
|
}
|
|
21929
23996
|
}
|
|
@@ -21991,7 +24058,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
|
|
|
21991
24058
|
function selectExistingHub(workspaceDir, allHubs, selector, wsLabel) {
|
|
21992
24059
|
if (selector) {
|
|
21993
24060
|
const match = allHubs.find(
|
|
21994
|
-
(h) => h.hubId === selector ||
|
|
24061
|
+
(h) => h.hubId === selector || path28.basename(h.hubFolder) === selector
|
|
21995
24062
|
);
|
|
21996
24063
|
if (!match) {
|
|
21997
24064
|
console.error(`No hub matching --hub ${selector} found in ${wsLabel}/.`);
|
|
@@ -22060,7 +24127,7 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
|
|
|
22060
24127
|
if (existingHubs.length > 0) {
|
|
22061
24128
|
console.error(`No pushable hub found in ${wsLabel}/ \u2014 only read-only production mirror folder(s). Edit the linked preview hub, pull one, or create a new hub with \`wayai create\`:`);
|
|
22062
24129
|
for (const h of existingHubs) {
|
|
22063
|
-
console.error(` ${
|
|
24130
|
+
console.error(` ${path28.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
|
|
22064
24131
|
}
|
|
22065
24132
|
} else {
|
|
22066
24133
|
console.error(`No hub folders found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` and run \`wayai push\` (or \`wayai create\`).`);
|
|
@@ -22074,10 +24141,10 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
|
|
|
22074
24141
|
console.error(`Multiple hub folders found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
|
|
22075
24142
|
}
|
|
22076
24143
|
for (const h of existingHubs) {
|
|
22077
|
-
console.error(` ${
|
|
24144
|
+
console.error(` ${path28.basename(h.hubFolder)} (${h.hubId})`);
|
|
22078
24145
|
}
|
|
22079
24146
|
for (const h of newHubs) {
|
|
22080
|
-
console.error(` ${
|
|
24147
|
+
console.error(` ${path28.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
|
|
22081
24148
|
}
|
|
22082
24149
|
if (hint) console.error(hint);
|
|
22083
24150
|
process.exit(1);
|
|
@@ -22104,7 +24171,7 @@ async function pushCommand(args2) {
|
|
|
22104
24171
|
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
22105
24172
|
const workspaceDir = resolveWorkspaceDir();
|
|
22106
24173
|
const wsLabel = hubsDirLabel(gitRoot);
|
|
22107
|
-
if (!
|
|
24174
|
+
if (!fs20.existsSync(workspaceDir)) {
|
|
22108
24175
|
console.error(`No ${wsLabel}/ directory found. Run \`wayai pull\` first or create hub files in ${wsLabel}/<hub>/hub.yaml.`);
|
|
22109
24176
|
const hint = communityFoldersHint(gitRoot, "push");
|
|
22110
24177
|
if (hint) console.error(hint);
|
|
@@ -22126,8 +24193,8 @@ async function pushCommand(args2) {
|
|
|
22126
24193
|
if (label) {
|
|
22127
24194
|
console.warn("Note: --label is only used when creating a new hub. To change an existing hub's label, use `wayai relabel`.");
|
|
22128
24195
|
}
|
|
22129
|
-
console.log(`Target hub: ${
|
|
22130
|
-
assertInScope("hubs", existing.hubId,
|
|
24196
|
+
console.log(`Target hub: ${path28.basename(existing.hubFolder)} (${existing.hubId})`);
|
|
24197
|
+
assertInScope("hubs", existing.hubId, path28.basename(existing.hubFolder));
|
|
22131
24198
|
await pushSingleHub(client, existing.hubId, existing.hubFolder, autoConfirm, organizationId);
|
|
22132
24199
|
return;
|
|
22133
24200
|
}
|
|
@@ -22165,8 +24232,8 @@ __export(pull_exports, {
|
|
|
22165
24232
|
resolveHubTarget: () => resolveHubTarget,
|
|
22166
24233
|
writeProductionMirror: () => writeProductionMirror2
|
|
22167
24234
|
});
|
|
22168
|
-
import * as
|
|
22169
|
-
import * as
|
|
24235
|
+
import * as fs21 from "fs";
|
|
24236
|
+
import * as path29 from "path";
|
|
22170
24237
|
function parseArgs7(args2) {
|
|
22171
24238
|
return { autoConfirm: args2.includes("--yes") || args2.includes("-y") };
|
|
22172
24239
|
}
|
|
@@ -22176,7 +24243,7 @@ function resolveHubTarget(workspaceDir, selector) {
|
|
|
22176
24243
|
const allHubs = scanWorkspaceHubs(workspaceDir);
|
|
22177
24244
|
if (selector) {
|
|
22178
24245
|
const match = allHubs.find(
|
|
22179
|
-
(h) => h.hubId === selector ||
|
|
24246
|
+
(h) => h.hubId === selector || path29.basename(h.hubFolder) === selector
|
|
22180
24247
|
);
|
|
22181
24248
|
if (match) return { hubId: match.hubId, hubFolder: match.hubFolder };
|
|
22182
24249
|
if (UUID_RE3.test(selector)) return { hubId: selector, hubFolder: null };
|
|
@@ -22195,7 +24262,7 @@ function resolveHubTarget(workspaceDir, selector) {
|
|
|
22195
24262
|
}
|
|
22196
24263
|
console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
|
|
22197
24264
|
for (const h of previewHubs) {
|
|
22198
|
-
console.error(` ${
|
|
24265
|
+
console.error(` ${path29.basename(h.hubFolder)} (${h.hubId})`);
|
|
22199
24266
|
}
|
|
22200
24267
|
process.exit(1);
|
|
22201
24268
|
}
|
|
@@ -22244,7 +24311,7 @@ async function pullCommand(args2) {
|
|
|
22244
24311
|
);
|
|
22245
24312
|
let wroteFiles = false;
|
|
22246
24313
|
if (!existingFolder && !resolveHubYamlPath(hubFolder)) {
|
|
22247
|
-
|
|
24314
|
+
fs21.mkdirSync(path29.dirname(hubFolder), { recursive: true });
|
|
22248
24315
|
console.log("Writing hub configuration...");
|
|
22249
24316
|
await materializeHubFolder(hubFolder, payload);
|
|
22250
24317
|
const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
|
|
@@ -22329,7 +24396,7 @@ ${why}`);
|
|
|
22329
24396
|
}
|
|
22330
24397
|
async function writeProductionMirror2(workspaceDir, prodPayload) {
|
|
22331
24398
|
const folder = resolveHubFolder(workspaceDir, prodPayload.hub_id, prodPayload.hub.name, "production", null, null);
|
|
22332
|
-
|
|
24399
|
+
fs21.mkdirSync(path29.dirname(folder), { recursive: true });
|
|
22333
24400
|
await materializeHubFolder(folder, prodPayload, { seedAgentContext: false });
|
|
22334
24401
|
const finalFolder = autoRenameHubFolder(folder, prodPayload.hub.name, "production", prodPayload.hub_id, null, null);
|
|
22335
24402
|
prependMirrorMarker(finalFolder, prodPayload.hub_id);
|
|
@@ -22345,13 +24412,13 @@ async function mirrorLinkedProduction2(client, workspaceDir, productionHubId, or
|
|
|
22345
24412
|
}
|
|
22346
24413
|
}
|
|
22347
24414
|
function prependMirrorMarker(hubFolder, productionHubId) {
|
|
22348
|
-
const hubYaml =
|
|
24415
|
+
const hubYaml = path29.join(hubFolder, "hub.yaml");
|
|
22349
24416
|
try {
|
|
22350
|
-
const content =
|
|
24417
|
+
const content = fs21.readFileSync(hubYaml, "utf-8");
|
|
22351
24418
|
if (content.startsWith(MIRROR_MARKER_PREFIX2)) return;
|
|
22352
24419
|
const marker = `${MIRROR_MARKER_PREFIX2} ${productionHubId}. Edits are ignored; push is blocked. Edit the linked preview hub instead.
|
|
22353
24420
|
`;
|
|
22354
|
-
|
|
24421
|
+
fs21.writeFileSync(hubYaml, marker + content, "utf-8");
|
|
22355
24422
|
} catch {
|
|
22356
24423
|
}
|
|
22357
24424
|
}
|
|
@@ -22399,8 +24466,8 @@ var create_exports = {};
|
|
|
22399
24466
|
__export(create_exports, {
|
|
22400
24467
|
createCommand: () => createCommand
|
|
22401
24468
|
});
|
|
22402
|
-
import * as
|
|
22403
|
-
import * as
|
|
24469
|
+
import * as path30 from "path";
|
|
24470
|
+
import * as fs22 from "fs";
|
|
22404
24471
|
function parseArgs8(args2) {
|
|
22405
24472
|
let autoConfirm = false;
|
|
22406
24473
|
let folderSelector;
|
|
@@ -22427,7 +24494,7 @@ async function createCommand(args2) {
|
|
|
22427
24494
|
const gitRoot = findGitRoot();
|
|
22428
24495
|
const wsLabel = hubsDirLabel(gitRoot);
|
|
22429
24496
|
if (gitRoot) warnLayoutOnce(gitRoot);
|
|
22430
|
-
if (!
|
|
24497
|
+
if (!fs22.existsSync(workspaceDir)) {
|
|
22431
24498
|
console.error(`No ${wsLabel}/ directory found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` first.`);
|
|
22432
24499
|
process.exit(1);
|
|
22433
24500
|
}
|
|
@@ -22436,7 +24503,7 @@ async function createCommand(args2) {
|
|
|
22436
24503
|
const resolution = resolveNewHubForCreate(workspaceDir, existingHubs, newHubs, folderSelector);
|
|
22437
24504
|
if (!resolution.ok) {
|
|
22438
24505
|
if (resolution.reason === "exists") {
|
|
22439
|
-
const folder =
|
|
24506
|
+
const folder = path30.basename(resolution.existing.hubFolder);
|
|
22440
24507
|
console.error(`Hub "${folder}" already exists (${resolution.existing.hubId}). Use \`wayai push --hub ${folder}\` to update it.`);
|
|
22441
24508
|
process.exit(1);
|
|
22442
24509
|
}
|
|
@@ -22450,7 +24517,7 @@ async function createCommand(args2) {
|
|
|
22450
24517
|
}
|
|
22451
24518
|
console.error(`Multiple new hub folders found in ${wsLabel}/. Pass the folder name (\`wayai create <folder>\`) or run from inside one:`);
|
|
22452
24519
|
for (const h of newHubs) {
|
|
22453
|
-
console.error(` ${
|
|
24520
|
+
console.error(` ${path30.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
|
|
22454
24521
|
}
|
|
22455
24522
|
process.exit(1);
|
|
22456
24523
|
}
|
|
@@ -22561,8 +24628,8 @@ var replicate_exports = {};
|
|
|
22561
24628
|
__export(replicate_exports, {
|
|
22562
24629
|
replicateCommand: () => replicateCommand
|
|
22563
24630
|
});
|
|
22564
|
-
import * as
|
|
22565
|
-
import * as
|
|
24631
|
+
import * as fs23 from "fs";
|
|
24632
|
+
import * as path31 from "path";
|
|
22566
24633
|
function parseArgs10(args2) {
|
|
22567
24634
|
let label;
|
|
22568
24635
|
let hubSelector;
|
|
@@ -22608,8 +24675,8 @@ async function replicateCommand(args2) {
|
|
|
22608
24675
|
payload.preview_label,
|
|
22609
24676
|
payload.branch_name
|
|
22610
24677
|
);
|
|
22611
|
-
const folderPreExisted =
|
|
22612
|
-
|
|
24678
|
+
const folderPreExisted = fs23.existsSync(hubFolder);
|
|
24679
|
+
fs23.mkdirSync(path31.dirname(hubFolder), { recursive: true });
|
|
22613
24680
|
const delta = await materializeHubFolder(hubFolder, payload);
|
|
22614
24681
|
hubFolder = autoRenameHubFolder(
|
|
22615
24682
|
hubFolder,
|
|
@@ -22621,8 +24688,8 @@ async function replicateCommand(args2) {
|
|
|
22621
24688
|
);
|
|
22622
24689
|
seedScopeIfEmpty("hubs", previewHubId);
|
|
22623
24690
|
if (folderPreExisted) printLocalFileChanges(delta);
|
|
22624
|
-
console.log(`Preview written to ${
|
|
22625
|
-
console.log(` Switch to it with: wayai use ${
|
|
24691
|
+
console.log(`Preview written to ${path31.basename(hubFolder)}`);
|
|
24692
|
+
console.log(` Switch to it with: wayai use ${path31.basename(hubFolder)}`);
|
|
22626
24693
|
}
|
|
22627
24694
|
var init_replicate = __esm({
|
|
22628
24695
|
"src/commands/replicate.ts"() {
|
|
@@ -22645,7 +24712,7 @@ var relabel_exports = {};
|
|
|
22645
24712
|
__export(relabel_exports, {
|
|
22646
24713
|
relabelCommand: () => relabelCommand
|
|
22647
24714
|
});
|
|
22648
|
-
import * as
|
|
24715
|
+
import * as path32 from "path";
|
|
22649
24716
|
function parseArgs11(args2) {
|
|
22650
24717
|
let label;
|
|
22651
24718
|
let clear = false;
|
|
@@ -22689,7 +24756,7 @@ async function relabelCommand(args2) {
|
|
|
22689
24756
|
} else {
|
|
22690
24757
|
console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
|
|
22691
24758
|
for (const h of previewHubs) {
|
|
22692
|
-
console.error(` ${
|
|
24759
|
+
console.error(` ${path32.basename(h.hubFolder)} (${h.hubId})`);
|
|
22693
24760
|
}
|
|
22694
24761
|
}
|
|
22695
24762
|
process.exit(1);
|
|
@@ -22698,7 +24765,7 @@ async function relabelCommand(args2) {
|
|
|
22698
24765
|
console.log("This is a read-only production mirror \u2014 production hubs have no preview label. Relabel the linked preview hub instead.");
|
|
22699
24766
|
return;
|
|
22700
24767
|
}
|
|
22701
|
-
assertInScope("hubs", target.hubId,
|
|
24768
|
+
assertInScope("hubs", target.hubId, path32.basename(target.hubFolder));
|
|
22702
24769
|
console.log(normalizedLabel ? `Setting preview label to "${normalizedLabel}"...` : "Clearing preview label...");
|
|
22703
24770
|
const { data } = await client.relabelPreview(target.hubId, normalizedLabel);
|
|
22704
24771
|
const row = data[0];
|
|
@@ -22714,7 +24781,7 @@ async function relabelCommand(args2) {
|
|
|
22714
24781
|
);
|
|
22715
24782
|
console.log(serverLabel ? `Preview label set to "${serverLabel}".` : "Preview label cleared.");
|
|
22716
24783
|
if (finalFolder !== target.hubFolder) {
|
|
22717
|
-
console.log(`Hub folder is now ${
|
|
24784
|
+
console.log(`Hub folder is now ${path32.basename(finalFolder)}`);
|
|
22718
24785
|
}
|
|
22719
24786
|
}
|
|
22720
24787
|
var init_relabel = __esm({
|
|
@@ -22740,7 +24807,7 @@ __export(publish_exports, {
|
|
|
22740
24807
|
renderHubDiff: () => renderHubDiff,
|
|
22741
24808
|
runPublish: () => runPublish
|
|
22742
24809
|
});
|
|
22743
|
-
import * as
|
|
24810
|
+
import * as path33 from "path";
|
|
22744
24811
|
function parseArgs12(args2) {
|
|
22745
24812
|
let autoConfirm = false;
|
|
22746
24813
|
let hubSelector;
|
|
@@ -22849,7 +24916,7 @@ async function publishCommand(args2) {
|
|
|
22849
24916
|
return;
|
|
22850
24917
|
}
|
|
22851
24918
|
}
|
|
22852
|
-
assertInScope("hubs", hubId, hubFolder ?
|
|
24919
|
+
assertInScope("hubs", hubId, hubFolder ? path33.basename(hubFolder) : void 0);
|
|
22853
24920
|
await runPublish(client, { hubId, localConfig, organizationId, autoConfirm });
|
|
22854
24921
|
}
|
|
22855
24922
|
var STATUS_STYLE, SECTION_TITLES;
|
|
@@ -22897,7 +24964,7 @@ var init_publish = __esm({
|
|
|
22897
24964
|
});
|
|
22898
24965
|
|
|
22899
24966
|
// src/lib/scope-selector.ts
|
|
22900
|
-
import * as
|
|
24967
|
+
import * as path34 from "path";
|
|
22901
24968
|
function findAddFlag(args2) {
|
|
22902
24969
|
return args2.find((arg) => arg === "--add" || arg.startsWith("--add="));
|
|
22903
24970
|
}
|
|
@@ -22954,7 +25021,7 @@ function resolveHubSelectorToId(gitRoot, selector) {
|
|
|
22954
25021
|
const match = findHubByFolderName(workspaceDir, selector);
|
|
22955
25022
|
if (match) return match.hubId;
|
|
22956
25023
|
const newHubs = scanNewHubs(workspaceDir);
|
|
22957
|
-
const pending = newHubs.find((h) =>
|
|
25024
|
+
const pending = newHubs.find((h) => path34.basename(h.hubFolder) === selector);
|
|
22958
25025
|
if (pending) {
|
|
22959
25026
|
throw expected(
|
|
22960
25027
|
`"${selector}" is a new hub that hasn't been created on the platform yet, so it has no id to bind to.
|
|
@@ -22965,9 +25032,9 @@ Create it first \u2014 the worktree scope picks it up automatically afterward:
|
|
|
22965
25032
|
throw expected(
|
|
22966
25033
|
[
|
|
22967
25034
|
`No hub matching "${selector}" found in ${hubsDirLabel(gitRoot)}/. Pass a UUID or a folder name from:`,
|
|
22968
|
-
...scanWorkspaceHubs(workspaceDir).map((h) => ` ${
|
|
25035
|
+
...scanWorkspaceHubs(workspaceDir).map((h) => ` ${path34.basename(h.hubFolder)} (${h.hubId})`),
|
|
22969
25036
|
...newHubs.map(
|
|
22970
|
-
(h) => ` ${
|
|
25037
|
+
(h) => ` ${path34.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path34.basename(h.hubFolder)}\`)`
|
|
22971
25038
|
)
|
|
22972
25039
|
].join("\n")
|
|
22973
25040
|
);
|
|
@@ -23057,11 +25124,11 @@ __export(migrate_exports, {
|
|
|
23057
25124
|
migrateCommand: () => migrateCommand
|
|
23058
25125
|
});
|
|
23059
25126
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
23060
|
-
import * as
|
|
23061
|
-
import * as
|
|
25127
|
+
import * as fs24 from "fs";
|
|
25128
|
+
import * as path35 from "path";
|
|
23062
25129
|
function isTracked(gitRoot, p) {
|
|
23063
25130
|
try {
|
|
23064
|
-
execFileSync3("git", ["ls-files", "--error-unmatch", "--",
|
|
25131
|
+
execFileSync3("git", ["ls-files", "--error-unmatch", "--", path35.relative(gitRoot, p)], {
|
|
23065
25132
|
cwd: gitRoot,
|
|
23066
25133
|
stdio: ["pipe", "pipe", "pipe"]
|
|
23067
25134
|
});
|
|
@@ -23071,10 +25138,10 @@ function isTracked(gitRoot, p) {
|
|
|
23071
25138
|
}
|
|
23072
25139
|
}
|
|
23073
25140
|
function moveDir(gitRoot, from, to) {
|
|
23074
|
-
|
|
25141
|
+
fs24.mkdirSync(path35.dirname(to), { recursive: true });
|
|
23075
25142
|
if (isTracked(gitRoot, from)) {
|
|
23076
25143
|
try {
|
|
23077
|
-
execFileSync3("git", ["mv",
|
|
25144
|
+
execFileSync3("git", ["mv", path35.relative(gitRoot, from), path35.relative(gitRoot, to)], {
|
|
23078
25145
|
cwd: gitRoot,
|
|
23079
25146
|
stdio: ["pipe", "pipe", "pipe"]
|
|
23080
25147
|
});
|
|
@@ -23082,7 +25149,7 @@ function moveDir(gitRoot, from, to) {
|
|
|
23082
25149
|
} catch {
|
|
23083
25150
|
}
|
|
23084
25151
|
}
|
|
23085
|
-
|
|
25152
|
+
fs24.renameSync(from, to);
|
|
23086
25153
|
return "fs";
|
|
23087
25154
|
}
|
|
23088
25155
|
async function migrateCommand(_args) {
|
|
@@ -23091,12 +25158,12 @@ async function migrateCommand(_args) {
|
|
|
23091
25158
|
console.error("Not inside a git repository.");
|
|
23092
25159
|
process.exit(1);
|
|
23093
25160
|
}
|
|
23094
|
-
const rel = (p) =>
|
|
23095
|
-
const newWs =
|
|
23096
|
-
const legacyWs =
|
|
23097
|
-
const legacyOrg =
|
|
23098
|
-
const newHubs =
|
|
23099
|
-
const newOrg =
|
|
25161
|
+
const rel = (p) => path35.relative(gitRoot, p);
|
|
25162
|
+
const newWs = path35.join(gitRoot, WAYAI_LAYOUT.wsDir);
|
|
25163
|
+
const legacyWs = path35.join(gitRoot, WAYAI_LAYOUT.legacy.wsDir);
|
|
25164
|
+
const legacyOrg = path35.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
|
|
25165
|
+
const newHubs = path35.join(newWs, WAYAI_LAYOUT.hubsSubdir);
|
|
25166
|
+
const newOrg = path35.join(newWs, WAYAI_LAYOUT.orgSubdir);
|
|
23100
25167
|
requireRealSubdirNoSymlink(gitRoot, newHubs, false);
|
|
23101
25168
|
requireRealSubdirNoSymlink(gitRoot, newOrg, false);
|
|
23102
25169
|
readRealFileOrThrow(gitRoot, workspaceManifestPath(gitRoot));
|
|
@@ -23249,12 +25316,12 @@ var send_message_exports = {};
|
|
|
23249
25316
|
__export(send_message_exports, {
|
|
23250
25317
|
sendMessageCommand: () => sendMessageCommand
|
|
23251
25318
|
});
|
|
23252
|
-
import * as
|
|
23253
|
-
import * as
|
|
25319
|
+
import * as fs25 from "fs";
|
|
25320
|
+
import * as path36 from "path";
|
|
23254
25321
|
function statAttachment(filePath) {
|
|
23255
25322
|
let stat2;
|
|
23256
25323
|
try {
|
|
23257
|
-
stat2 =
|
|
25324
|
+
stat2 = fs25.statSync(filePath);
|
|
23258
25325
|
} catch {
|
|
23259
25326
|
console.error(`Error: file not found: ${filePath}`);
|
|
23260
25327
|
process.exit(1);
|
|
@@ -23266,11 +25333,11 @@ function statAttachment(filePath) {
|
|
|
23266
25333
|
return { filePath, size: stat2.size };
|
|
23267
25334
|
}
|
|
23268
25335
|
function readAttachment(filePath, size) {
|
|
23269
|
-
const fileName =
|
|
23270
|
-
const ext =
|
|
25336
|
+
const fileName = path36.basename(filePath);
|
|
25337
|
+
const ext = path36.extname(fileName).replace(/^\./, "");
|
|
23271
25338
|
return {
|
|
23272
25339
|
file_name: fileName,
|
|
23273
|
-
file_binary:
|
|
25340
|
+
file_binary: fs25.readFileSync(filePath).toString("base64"),
|
|
23274
25341
|
file_size: size,
|
|
23275
25342
|
...ext && { file_extension: ext }
|
|
23276
25343
|
};
|
|
@@ -23317,7 +25384,7 @@ async function sendMessageCommand(args2) {
|
|
|
23317
25384
|
}
|
|
23318
25385
|
const stats = filePaths.map(statAttachment);
|
|
23319
25386
|
const filesTotal = stats.reduce((sum, s) => sum + base64Length(s.size), 0);
|
|
23320
|
-
const fileDetail = () => stats.map((s) => `${
|
|
25387
|
+
const fileDetail = () => stats.map((s) => `${path36.basename(s.filePath)} ${asMb(s.size)} MB`).join(", ");
|
|
23321
25388
|
if (filesTotal > MAX_MESSAGE_BODY_BYTES) {
|
|
23322
25389
|
console.error(
|
|
23323
25390
|
`Error: ${fileDetail()} encode to ~${asMb(filesTotal)} MB, over the ${asMb(MAX_MESSAGE_BODY_BYTES)} MB per-request limit.`
|
|
@@ -23325,10 +25392,10 @@ async function sendMessageCommand(args2) {
|
|
|
23325
25392
|
console.error("Base64 inflates a file by about a third, so ~7 MB of files is the practical ceiling.");
|
|
23326
25393
|
process.exit(1);
|
|
23327
25394
|
}
|
|
23328
|
-
const audioStats = stats.filter((s) => isAudioAttachmentFileName(
|
|
25395
|
+
const audioStats = stats.filter((s) => isAudioAttachmentFileName(path36.basename(s.filePath)));
|
|
23329
25396
|
if (audioStats.length > 1) {
|
|
23330
25397
|
console.error(
|
|
23331
|
-
`Error: at most one audio file per message (got ${audioStats.length}: ${audioStats.map((s) =>
|
|
25398
|
+
`Error: at most one audio file per message (got ${audioStats.length}: ${audioStats.map((s) => path36.basename(s.filePath)).join(", ")}).`
|
|
23332
25399
|
);
|
|
23333
25400
|
console.error("Audio is transcribed via the hub's STT connection, and only one file per message is transcribed.");
|
|
23334
25401
|
process.exit(1);
|
|
@@ -23336,7 +25403,7 @@ async function sendMessageCommand(args2) {
|
|
|
23336
25403
|
const oversizeAudio = audioStats.find((s) => base64Length(s.size) > MAX_AUDIO_FILE_BASE64_BYTES);
|
|
23337
25404
|
if (oversizeAudio) {
|
|
23338
25405
|
console.error(
|
|
23339
|
-
`Error: ${
|
|
25406
|
+
`Error: ${path36.basename(oversizeAudio.filePath)} (${asMb(oversizeAudio.size)} MB) is over the ~${asMb(MAX_AUDIO_FILE_BASE64_BYTES * 3 / 4)} MB audio limit for transcription.`
|
|
23340
25407
|
);
|
|
23341
25408
|
process.exit(1);
|
|
23342
25409
|
}
|
|
@@ -24812,133 +26879,6 @@ var init_evals = __esm({
|
|
|
24812
26879
|
}
|
|
24813
26880
|
});
|
|
24814
26881
|
|
|
24815
|
-
// src/lib/eval-session-control.ts
|
|
24816
|
-
function formatEvalSessionStopCommand(sessionId, hubId) {
|
|
24817
|
-
return `wayai eval session stop ${sessionId} --hub ${hubId}`;
|
|
24818
|
-
}
|
|
24819
|
-
function formatEvalResultsCommand(sessionId, hubId) {
|
|
24820
|
-
return `wayai eval-results --session ${sessionId} --hub ${hubId}`;
|
|
24821
|
-
}
|
|
24822
|
-
function describeSeedRelease(status) {
|
|
24823
|
-
if (status === "pending") {
|
|
24824
|
-
return "A turn that started before the stop is still finishing \u2014 its fixture stays reserved until that turn settles.";
|
|
24825
|
-
}
|
|
24826
|
-
if (status === "releasing") return "Its fixture is being released.";
|
|
24827
|
-
return "A turn already in progress may still be finishing \u2014 wait for it before starting another run.";
|
|
24828
|
-
}
|
|
24829
|
-
function classifyStopOutcome(response) {
|
|
24830
|
-
return response.data.stopped ? { kind: "cancelled", seedRelease: response.data.seed_release_status } : { kind: "already_finished", status: response.data.session.session_status };
|
|
24831
|
-
}
|
|
24832
|
-
function isTerminalStopFailure(err) {
|
|
24833
|
-
return err instanceof ApiError && err.status >= 400 && err.status < 500 && err.status !== 408 && err.status !== 429;
|
|
24834
|
-
}
|
|
24835
|
-
async function stopEvalSessionWithRetries(stop, opts = {}) {
|
|
24836
|
-
const attempts = opts.attempts ?? STOP_ATTEMPTS;
|
|
24837
|
-
const attemptTimeoutMs = opts.attemptTimeoutMs ?? STOP_ATTEMPT_TIMEOUT_MS;
|
|
24838
|
-
const backoffMs = opts.backoffMs ?? STOP_BACKOFF_MS;
|
|
24839
|
-
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
24840
|
-
let lastError = new Error("Stop was never attempted");
|
|
24841
|
-
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
24842
|
-
let timeout;
|
|
24843
|
-
try {
|
|
24844
|
-
return await Promise.race([
|
|
24845
|
-
stop(),
|
|
24846
|
-
new Promise((_resolve, reject) => {
|
|
24847
|
-
timeout = setTimeout(
|
|
24848
|
-
() => reject(new Error(`Stop attempt ${attempt} timed out after ${attemptTimeoutMs}ms`)),
|
|
24849
|
-
attemptTimeoutMs
|
|
24850
|
-
);
|
|
24851
|
-
})
|
|
24852
|
-
]);
|
|
24853
|
-
} catch (err) {
|
|
24854
|
-
lastError = err;
|
|
24855
|
-
if (isTerminalStopFailure(err)) break;
|
|
24856
|
-
} finally {
|
|
24857
|
-
if (timeout !== void 0) clearTimeout(timeout);
|
|
24858
|
-
}
|
|
24859
|
-
const delay2 = backoffMs[attempt - 1];
|
|
24860
|
-
if (attempt < attempts && delay2) await sleep3(delay2);
|
|
24861
|
-
}
|
|
24862
|
-
throw lastError;
|
|
24863
|
-
}
|
|
24864
|
-
function installEvalSignalHandlers(input) {
|
|
24865
|
-
const exit = input.exit ?? ((code) => process.exit(code));
|
|
24866
|
-
const manualStopCommand = formatEvalSessionStopCommand(input.sessionId, input.hubId);
|
|
24867
|
-
let signalCount = 0;
|
|
24868
|
-
let disposed = false;
|
|
24869
|
-
let settle;
|
|
24870
|
-
const settled = new Promise((resolve12) => {
|
|
24871
|
-
settle = resolve12;
|
|
24872
|
-
});
|
|
24873
|
-
const listeners = /* @__PURE__ */ new Map();
|
|
24874
|
-
const dispose = () => {
|
|
24875
|
-
if (disposed) return;
|
|
24876
|
-
disposed = true;
|
|
24877
|
-
for (const [signal, listener] of listeners) process.off(signal, listener);
|
|
24878
|
-
};
|
|
24879
|
-
const finish = (exitCode) => {
|
|
24880
|
-
dispose();
|
|
24881
|
-
settle();
|
|
24882
|
-
exit(exitCode);
|
|
24883
|
-
};
|
|
24884
|
-
const handle = (signal) => {
|
|
24885
|
-
signalCount += 1;
|
|
24886
|
-
const signalExitCode = SIGNAL_EXIT_CODE[signal];
|
|
24887
|
-
if (signalCount > 1) {
|
|
24888
|
-
console.error("\nSecond signal received; exiting immediately. The remote eval session may still be running.");
|
|
24889
|
-
console.error(`Run: ${manualStopCommand}`);
|
|
24890
|
-
finish(signalExitCode);
|
|
24891
|
-
return;
|
|
24892
|
-
}
|
|
24893
|
-
console.error(`
|
|
24894
|
-
${signal} received; stopping remote eval session ${input.sessionId}...`);
|
|
24895
|
-
void stopEvalSessionWithRetries(input.stop).then((response) => {
|
|
24896
|
-
if (signalCount > 1) return;
|
|
24897
|
-
const outcome = classifyStopOutcome(response);
|
|
24898
|
-
if (outcome.kind === "already_finished") {
|
|
24899
|
-
console.error(`Session had already finished (${outcome.status}) \u2014 nothing was cancelled.`);
|
|
24900
|
-
console.error(`See its results: ${formatEvalResultsCommand(input.sessionId, input.hubId)}`);
|
|
24901
|
-
} else {
|
|
24902
|
-
console.error("Remote session cancellation requested.");
|
|
24903
|
-
console.error(describeSeedRelease(outcome.seedRelease));
|
|
24904
|
-
}
|
|
24905
|
-
finish(signalExitCode);
|
|
24906
|
-
}).catch((err) => {
|
|
24907
|
-
if (signalCount > 1) return;
|
|
24908
|
-
console.error(`Failed to confirm remote cancellation: ${extractApiMessage(err)}`);
|
|
24909
|
-
console.error(`The session may still be running. Run: ${manualStopCommand}`);
|
|
24910
|
-
finish(1);
|
|
24911
|
-
});
|
|
24912
|
-
};
|
|
24913
|
-
for (const signal of HANDLED_SIGNALS) {
|
|
24914
|
-
const listener = () => handle(signal);
|
|
24915
|
-
listeners.set(signal, listener);
|
|
24916
|
-
process.on(signal, listener);
|
|
24917
|
-
}
|
|
24918
|
-
return {
|
|
24919
|
-
manualStopCommand,
|
|
24920
|
-
async interrupted() {
|
|
24921
|
-
if (signalCount === 0) return false;
|
|
24922
|
-
await settled;
|
|
24923
|
-
return true;
|
|
24924
|
-
},
|
|
24925
|
-
dispose
|
|
24926
|
-
};
|
|
24927
|
-
}
|
|
24928
|
-
var SIGNAL_EXIT_CODE, HANDLED_SIGNALS, STOP_ATTEMPTS, STOP_ATTEMPT_TIMEOUT_MS, STOP_BACKOFF_MS;
|
|
24929
|
-
var init_eval_session_control = __esm({
|
|
24930
|
-
"src/lib/eval-session-control.ts"() {
|
|
24931
|
-
"use strict";
|
|
24932
|
-
init_api_client();
|
|
24933
|
-
init_errors2();
|
|
24934
|
-
SIGNAL_EXIT_CODE = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 };
|
|
24935
|
-
HANDLED_SIGNALS = Object.keys(SIGNAL_EXIT_CODE);
|
|
24936
|
-
STOP_ATTEMPTS = 3;
|
|
24937
|
-
STOP_ATTEMPT_TIMEOUT_MS = 1e4;
|
|
24938
|
-
STOP_BACKOFF_MS = [500, 1e3];
|
|
24939
|
-
}
|
|
24940
|
-
});
|
|
24941
|
-
|
|
24942
26882
|
// src/lib/eval-fixture-queue.ts
|
|
24943
26883
|
function isQueueableLaunchError(err) {
|
|
24944
26884
|
const code = extractApiErrorCode(err);
|
|
@@ -25491,7 +27431,7 @@ var init_eval_results = __esm({
|
|
|
25491
27431
|
import { createHash as createHash4 } from "crypto";
|
|
25492
27432
|
import { mkdirSync as mkdirSync12, readFileSync as readFileSync17, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
25493
27433
|
import { homedir as homedir5 } from "os";
|
|
25494
|
-
import { dirname as
|
|
27434
|
+
import { dirname as dirname12, isAbsolute as isAbsolute2, join as join31, resolve as resolve10 } from "path";
|
|
25495
27435
|
function parseWav(buf, name = "clip") {
|
|
25496
27436
|
if (buf.length < 12 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
|
|
25497
27437
|
throw new Error(`${name} is not a WAV file.`);
|
|
@@ -25530,7 +27470,7 @@ function parseWav(buf, name = "clip") {
|
|
|
25530
27470
|
throw new Error(`${name} holds no audio data.`);
|
|
25531
27471
|
}
|
|
25532
27472
|
function defaultClipCacheDir() {
|
|
25533
|
-
return
|
|
27473
|
+
return join31(homedir5(), ".wayai", "call-clips");
|
|
25534
27474
|
}
|
|
25535
27475
|
function pcmClip(buf) {
|
|
25536
27476
|
const samples = new Int16Array(Math.floor(buf.length / 2));
|
|
@@ -25579,15 +27519,15 @@ var init_caller_audio = __esm({
|
|
|
25579
27519
|
}
|
|
25580
27520
|
describe;
|
|
25581
27521
|
async clipFor(line) {
|
|
25582
|
-
const
|
|
25583
|
-
if (!
|
|
27522
|
+
const path41 = line.clip ? isAbsolute2(line.clip) ? line.clip : resolve10(this.planDir, line.clip) : this.dir ? join31(this.dir, `${line.n}.wav`) : null;
|
|
27523
|
+
if (!path41) throw new Error(`Line ${line.n} has no clip: give --clips <dir>, or a "clip" for it in the plan.`);
|
|
25584
27524
|
let buf;
|
|
25585
27525
|
try {
|
|
25586
|
-
buf = readFileSync17(
|
|
27526
|
+
buf = readFileSync17(path41);
|
|
25587
27527
|
} catch {
|
|
25588
|
-
throw new Error(`Line ${line.n}'s clip ${
|
|
27528
|
+
throw new Error(`Line ${line.n}'s clip ${path41} cannot be read.`);
|
|
25589
27529
|
}
|
|
25590
|
-
return parseWav(buf,
|
|
27530
|
+
return parseWav(buf, path41);
|
|
25591
27531
|
}
|
|
25592
27532
|
};
|
|
25593
27533
|
OPENAI_PCM_SAMPLE_RATE = 24e3;
|
|
@@ -25614,20 +27554,20 @@ var init_caller_audio = __esm({
|
|
|
25614
27554
|
const key = createHash4("sha256").update(`${this.model}
|
|
25615
27555
|
${this.voice}
|
|
25616
27556
|
${text}`).digest("hex");
|
|
25617
|
-
return
|
|
27557
|
+
return join31(this.cacheDir, `${key}.pcm`);
|
|
25618
27558
|
}
|
|
25619
27559
|
clipFor(line) {
|
|
25620
|
-
const
|
|
25621
|
-
let pending = this.inFlight.get(
|
|
27560
|
+
const path41 = this.cachePath(line.say);
|
|
27561
|
+
let pending = this.inFlight.get(path41);
|
|
25622
27562
|
if (!pending) {
|
|
25623
|
-
pending = this.readOrSynthesize(line,
|
|
25624
|
-
this.inFlight.set(
|
|
27563
|
+
pending = this.readOrSynthesize(line, path41);
|
|
27564
|
+
this.inFlight.set(path41, pending);
|
|
25625
27565
|
}
|
|
25626
27566
|
return pending;
|
|
25627
27567
|
}
|
|
25628
|
-
async readOrSynthesize(line,
|
|
27568
|
+
async readOrSynthesize(line, path41) {
|
|
25629
27569
|
try {
|
|
25630
|
-
return pcmClip(readFileSync17(
|
|
27570
|
+
return pcmClip(readFileSync17(path41));
|
|
25631
27571
|
} catch {
|
|
25632
27572
|
}
|
|
25633
27573
|
const res = await this.doFetch("https://api.openai.com/v1/audio/speech", {
|
|
@@ -25641,10 +27581,10 @@ ${text}`).digest("hex");
|
|
|
25641
27581
|
}
|
|
25642
27582
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
25643
27583
|
if (buf.length < 2) throw new Error(`OpenAI speech synthesis returned no audio for line ${line.n}.`);
|
|
25644
|
-
mkdirSync12(
|
|
25645
|
-
const tmp = `${
|
|
27584
|
+
mkdirSync12(dirname12(path41), { recursive: true });
|
|
27585
|
+
const tmp = `${path41}.${process.pid}.tmp`;
|
|
25646
27586
|
writeFileSync7(tmp, buf);
|
|
25647
|
-
renameSync5(tmp,
|
|
27587
|
+
renameSync5(tmp, path41);
|
|
25648
27588
|
return pcmClip(buf);
|
|
25649
27589
|
}
|
|
25650
27590
|
};
|
|
@@ -27134,7 +29074,7 @@ __export(eval_call_exports, {
|
|
|
27134
29074
|
suiteExitCode: () => suiteExitCode
|
|
27135
29075
|
});
|
|
27136
29076
|
import { accessSync, constants as fsConstants, existsSync as existsSync16, readFileSync as readFileSync18, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
27137
|
-
import { dirname as
|
|
29077
|
+
import { dirname as dirname13, resolve as resolve11 } from "path";
|
|
27138
29078
|
function fail(message) {
|
|
27139
29079
|
throw new Error(message);
|
|
27140
29080
|
}
|
|
@@ -27209,15 +29149,15 @@ function parseEvalCallArgs(args2) {
|
|
|
27209
29149
|
if (parsed.jsonPath) checkReportPath(parsed.jsonPath);
|
|
27210
29150
|
return parsed;
|
|
27211
29151
|
}
|
|
27212
|
-
function checkReportPath(
|
|
27213
|
-
const target = resolve11(
|
|
27214
|
-
const dir =
|
|
27215
|
-
if (!existsSync16(dir) || !statSync8(dir).isDirectory()) fail(`--json ${
|
|
27216
|
-
if (existsSync16(target) && statSync8(target).isDirectory()) fail(`--json ${
|
|
29152
|
+
function checkReportPath(path41) {
|
|
29153
|
+
const target = resolve11(path41);
|
|
29154
|
+
const dir = dirname13(target);
|
|
29155
|
+
if (!existsSync16(dir) || !statSync8(dir).isDirectory()) fail(`--json ${path41}: the folder ${dir} does not exist.`);
|
|
29156
|
+
if (existsSync16(target) && statSync8(target).isDirectory()) fail(`--json ${path41} is a folder; name a file.`);
|
|
27217
29157
|
try {
|
|
27218
29158
|
accessSync(existsSync16(target) ? target : dir, fsConstants.W_OK);
|
|
27219
29159
|
} catch {
|
|
27220
|
-
fail(`--json ${
|
|
29160
|
+
fail(`--json ${path41} cannot be written here.`);
|
|
27221
29161
|
}
|
|
27222
29162
|
}
|
|
27223
29163
|
function suiteExitCode(report2, runs, reportWritten) {
|
|
@@ -27228,7 +29168,7 @@ function formatCallPages(runs) {
|
|
|
27228
29168
|
return pages.length > 0 ? ["Call pages (the transcript, and the recording once it is ready):", ...pages] : [];
|
|
27229
29169
|
}
|
|
27230
29170
|
function chooseAudioSource(options, plan, env = process.env) {
|
|
27231
|
-
const planDir = options.planPath ?
|
|
29171
|
+
const planDir = options.planPath ? dirname13(resolve11(options.planPath)) : process.cwd();
|
|
27232
29172
|
const planHasClips = Object.values(plan.lines).some((line) => line.clip);
|
|
27233
29173
|
if (options.clipsDir || planHasClips) {
|
|
27234
29174
|
if (options.clipsDir && !existsSync16(options.clipsDir)) fail(`--clips ${options.clipsDir} does not exist.`);
|
|
@@ -27427,9 +29367,9 @@ var eval_capture_exports = {};
|
|
|
27427
29367
|
__export(eval_capture_exports, {
|
|
27428
29368
|
evalCaptureCommand: () => evalCaptureCommand
|
|
27429
29369
|
});
|
|
27430
|
-
import * as
|
|
27431
|
-
import * as
|
|
27432
|
-
import * as
|
|
29370
|
+
import * as fs26 from "fs";
|
|
29371
|
+
import * as path37 from "path";
|
|
29372
|
+
import * as yaml15 from "js-yaml";
|
|
27433
29373
|
function isValidSetName(name) {
|
|
27434
29374
|
if (name.length === 0 || name === "." || name === "..") return false;
|
|
27435
29375
|
if (name.startsWith(".")) return false;
|
|
@@ -27494,25 +29434,25 @@ async function evalCaptureCommand(args2) {
|
|
|
27494
29434
|
const setFolderName = targetSetName;
|
|
27495
29435
|
const scenarioName = parsed.evalName ?? `Capture ${parsed.conversationId.slice(0, 8)}`;
|
|
27496
29436
|
const slug = slugify2(scenarioName);
|
|
27497
|
-
const evalsDir =
|
|
27498
|
-
const targetDir =
|
|
27499
|
-
const targetPath =
|
|
27500
|
-
if (!targetPath.startsWith(evalsDir +
|
|
27501
|
-
console.error(`Resolved path "${
|
|
29437
|
+
const evalsDir = path37.join(hubFolder, "evals");
|
|
29438
|
+
const targetDir = path37.join(evalsDir, setFolderName);
|
|
29439
|
+
const targetPath = path37.join(targetDir, `${slug}.yaml`);
|
|
29440
|
+
if (!targetPath.startsWith(evalsDir + path37.sep)) {
|
|
29441
|
+
console.error(`Resolved path "${path37.relative(hubFolder, targetPath)}" escapes evals/. Aborting.`);
|
|
27502
29442
|
process.exit(1);
|
|
27503
29443
|
}
|
|
27504
29444
|
if (!ensureRealSubdirNoSymlink(hubFolder, targetDir, false)) {
|
|
27505
|
-
console.error(`${
|
|
29445
|
+
console.error(`${path37.relative(hubFolder, targetDir)} is reached through a symlink. Aborting.`);
|
|
27506
29446
|
process.exit(1);
|
|
27507
29447
|
}
|
|
27508
29448
|
let targetTaken = true;
|
|
27509
29449
|
try {
|
|
27510
|
-
|
|
29450
|
+
fs26.lstatSync(targetPath);
|
|
27511
29451
|
} catch {
|
|
27512
29452
|
targetTaken = false;
|
|
27513
29453
|
}
|
|
27514
29454
|
if (targetTaken) {
|
|
27515
|
-
console.error(`File already exists: ${
|
|
29455
|
+
console.error(`File already exists: ${path37.relative(hubFolder, targetPath)}. Use --name to choose a different name.`);
|
|
27516
29456
|
process.exit(1);
|
|
27517
29457
|
}
|
|
27518
29458
|
console.log("Resolving scenario set...");
|
|
@@ -27544,14 +29484,14 @@ async function evalCaptureCommand(args2) {
|
|
|
27544
29484
|
...captured.evaluator_instructions ? { evaluator_instructions: captured.evaluator_instructions } : {}
|
|
27545
29485
|
};
|
|
27546
29486
|
const yamlObj = buildEvalYamlObject(evalEntry, slug);
|
|
27547
|
-
const outcome = createFileNoFollow(hubFolder, targetPath, Buffer.from(
|
|
29487
|
+
const outcome = createFileNoFollow(hubFolder, targetPath, Buffer.from(yaml15.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8"));
|
|
27548
29488
|
if (outcome !== "created") {
|
|
27549
29489
|
console.error(
|
|
27550
|
-
`Could not write ${
|
|
29490
|
+
`Could not write ${path37.relative(hubFolder, targetPath)} (${outcome === "exists" ? "it appeared meanwhile" : "its folder is reached through a symlink"}). The scenario was created on the platform as ${captured.eval_id} \u2014 run \`wayai pull\` to fetch it.`
|
|
27551
29491
|
);
|
|
27552
29492
|
process.exit(1);
|
|
27553
29493
|
}
|
|
27554
|
-
const relPath =
|
|
29494
|
+
const relPath = path37.relative(process.cwd(), targetPath);
|
|
27555
29495
|
console.log(`
|
|
27556
29496
|
Wrote ${relPath}`);
|
|
27557
29497
|
console.log("Run `wayai pull` to refresh the agent display name, then commit. The scenario is already on the platform.");
|
|
@@ -28931,20 +30871,20 @@ var init_set_connection_credential = __esm({
|
|
|
28931
30871
|
});
|
|
28932
30872
|
|
|
28933
30873
|
// src/lib/org-workspace.ts
|
|
28934
|
-
import * as
|
|
28935
|
-
import * as
|
|
28936
|
-
import * as
|
|
30874
|
+
import * as fs27 from "fs";
|
|
30875
|
+
import * as path38 from "path";
|
|
30876
|
+
import * as yaml16 from "js-yaml";
|
|
28937
30877
|
function getOrgDir(gitRoot) {
|
|
28938
30878
|
return resolveLayout(gitRoot).orgDir;
|
|
28939
30879
|
}
|
|
28940
30880
|
function orgManifestExists(orgDir) {
|
|
28941
|
-
return
|
|
30881
|
+
return fs27.existsSync(path38.join(orgDir, ORG_MANIFEST_NAME));
|
|
28942
30882
|
}
|
|
28943
30883
|
function parseOrgResources(orgDir) {
|
|
28944
|
-
const resourcesDir =
|
|
30884
|
+
const resourcesDir = path38.join(orgDir, "resources");
|
|
28945
30885
|
requireRealSubdirNoSymlink(orgDir, resourcesDir, false);
|
|
28946
|
-
const manifestText = readRealFileOrThrow(orgDir,
|
|
28947
|
-
const manifest = (manifestText !== null ?
|
|
30886
|
+
const manifestText = readRealFileOrThrow(orgDir, path38.join(orgDir, ORG_MANIFEST_NAME));
|
|
30887
|
+
const manifest = (manifestText !== null ? yaml16.load(manifestText) : null) ?? {};
|
|
28948
30888
|
const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
|
|
28949
30889
|
const resources = rawResources.map((res) => {
|
|
28950
30890
|
const resource = { name: res.name };
|
|
@@ -28956,9 +30896,9 @@ function parseOrgResources(orgDir) {
|
|
|
28956
30896
|
if (res.environment) resource.environment = res.environment;
|
|
28957
30897
|
if (Array.isArray(res.tags)) resource.tags = res.tags;
|
|
28958
30898
|
if (Array.isArray(res.folders)) resource.folders = res.folders;
|
|
28959
|
-
const resDir =
|
|
30899
|
+
const resDir = path38.join(resourcesDir, slugify2(resource.name));
|
|
28960
30900
|
requireRealSubdirNoSymlink(orgDir, resDir, false);
|
|
28961
|
-
if (
|
|
30901
|
+
if (fs27.existsSync(resDir)) {
|
|
28962
30902
|
const files = scanResourceFiles(resDir, "");
|
|
28963
30903
|
if (files.length > 0) resource.files = files;
|
|
28964
30904
|
}
|
|
@@ -28967,8 +30907,8 @@ function parseOrgResources(orgDir) {
|
|
|
28967
30907
|
return { version: 1, resources };
|
|
28968
30908
|
}
|
|
28969
30909
|
function writeOrgResources(orgDir, payload) {
|
|
28970
|
-
|
|
28971
|
-
const resourcesDir =
|
|
30910
|
+
fs27.mkdirSync(orgDir, { recursive: true });
|
|
30911
|
+
const resourcesDir = path38.join(orgDir, "resources");
|
|
28972
30912
|
requireRealSubdirNoSymlink(orgDir, resourcesDir, false);
|
|
28973
30913
|
const resources = payload.resources ?? [];
|
|
28974
30914
|
const manifestResources = resources.map((r) => {
|
|
@@ -28977,19 +30917,19 @@ function writeOrgResources(orgDir, payload) {
|
|
|
28977
30917
|
});
|
|
28978
30918
|
writeFileNoFollow(
|
|
28979
30919
|
orgDir,
|
|
28980
|
-
|
|
28981
|
-
Buffer.from(
|
|
30920
|
+
path38.join(orgDir, ORG_MANIFEST_NAME),
|
|
30921
|
+
Buffer.from(yaml16.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS), "utf-8")
|
|
28982
30922
|
);
|
|
28983
30923
|
const currentSlugs = /* @__PURE__ */ new Set();
|
|
28984
30924
|
for (const resource of resources) {
|
|
28985
30925
|
const resSlug = slugify2(resource.name);
|
|
28986
30926
|
currentSlugs.add(resSlug);
|
|
28987
|
-
writeResourceFileTree(
|
|
30927
|
+
writeResourceFileTree(path38.join(resourcesDir, resSlug), resource.files || [], orgDir);
|
|
28988
30928
|
}
|
|
28989
|
-
if (
|
|
28990
|
-
for (const entry of
|
|
30929
|
+
if (fs27.existsSync(resourcesDir)) {
|
|
30930
|
+
for (const entry of fs27.readdirSync(resourcesDir, { withFileTypes: true })) {
|
|
28991
30931
|
if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
|
|
28992
|
-
|
|
30932
|
+
fs27.rmSync(path38.join(resourcesDir, entry.name), { recursive: true, force: true });
|
|
28993
30933
|
}
|
|
28994
30934
|
}
|
|
28995
30935
|
}
|
|
@@ -28998,7 +30938,7 @@ async function downloadOrgBinaryFiles(orgDir, payload) {
|
|
|
28998
30938
|
let count = 0;
|
|
28999
30939
|
for (const resource of payload.resources ?? []) {
|
|
29000
30940
|
if (!resource.files) continue;
|
|
29001
|
-
const resDir =
|
|
30941
|
+
const resDir = path38.join(orgDir, "resources", slugify2(resource.name));
|
|
29002
30942
|
count += await downloadBinaryFiles(resDir, resource.files, orgDir);
|
|
29003
30943
|
}
|
|
29004
30944
|
if (count > 0) console.log(`Downloaded ${count} binary resource file(s).`);
|
|
@@ -29621,8 +31561,8 @@ var init_report_edit_args = __esm({
|
|
|
29621
31561
|
});
|
|
29622
31562
|
|
|
29623
31563
|
// src/lib/file-map.ts
|
|
29624
|
-
import * as
|
|
29625
|
-
import * as
|
|
31564
|
+
import * as fs28 from "fs";
|
|
31565
|
+
import * as path39 from "path";
|
|
29626
31566
|
function isSafeRelPath(rel) {
|
|
29627
31567
|
if (rel.length === 0 || rel.length > 300) return false;
|
|
29628
31568
|
if (rel.startsWith("/") || rel.includes("\\")) return false;
|
|
@@ -29637,9 +31577,9 @@ function writeFileMap(targetDir, files) {
|
|
|
29637
31577
|
if (!isSafeRelPath(rel)) {
|
|
29638
31578
|
throw new Error(`Refusing to write unsafe path: ${rel}`);
|
|
29639
31579
|
}
|
|
29640
|
-
const abs =
|
|
29641
|
-
|
|
29642
|
-
|
|
31580
|
+
const abs = path39.join(targetDir, rel);
|
|
31581
|
+
fs28.mkdirSync(path39.dirname(abs), { recursive: true });
|
|
31582
|
+
fs28.writeFileSync(abs, body, "utf-8");
|
|
29643
31583
|
written.push(rel);
|
|
29644
31584
|
}
|
|
29645
31585
|
return written;
|
|
@@ -29655,8 +31595,8 @@ var admin_exports = {};
|
|
|
29655
31595
|
__export(admin_exports, {
|
|
29656
31596
|
adminCommand: () => adminCommand
|
|
29657
31597
|
});
|
|
29658
|
-
import * as
|
|
29659
|
-
import * as
|
|
31598
|
+
import * as fs29 from "fs";
|
|
31599
|
+
import * as path40 from "path";
|
|
29660
31600
|
async function adminCommand(args2) {
|
|
29661
31601
|
const [group, ...afterGroup] = args2;
|
|
29662
31602
|
if (!group) {
|
|
@@ -30095,7 +32035,7 @@ async function runArchiveRead(positional, flagArgs) {
|
|
|
30095
32035
|
exitOnApiError(err);
|
|
30096
32036
|
throw err;
|
|
30097
32037
|
}
|
|
30098
|
-
|
|
32038
|
+
fs29.writeFileSync(outPath, zip);
|
|
30099
32039
|
console.log(`Wrote ${zip.byteLength} bytes to ${outPath}`);
|
|
30100
32040
|
return;
|
|
30101
32041
|
}
|
|
@@ -30237,13 +32177,13 @@ async function runSkillInstall(positional) {
|
|
|
30237
32177
|
throw err;
|
|
30238
32178
|
}
|
|
30239
32179
|
const root = findGitRoot() ?? process.cwd();
|
|
30240
|
-
const present = HARNESS_SKILL_DIRS.filter((dir) =>
|
|
32180
|
+
const present = HARNESS_SKILL_DIRS.filter((dir) => fs29.existsSync(path40.join(root, dir)));
|
|
30241
32181
|
const targets = present.length > 0 ? present : HARNESS_SKILL_DIRS;
|
|
30242
32182
|
const fileCount = Object.keys(res.files).length;
|
|
30243
32183
|
const relDirs = targets.map((harness) => {
|
|
30244
|
-
const targetDir =
|
|
32184
|
+
const targetDir = path40.join(root, harness, "skills", name);
|
|
30245
32185
|
writeFileMap(targetDir, res.files);
|
|
30246
|
-
return `${
|
|
32186
|
+
return `${path40.relative(root, targetDir)}/`;
|
|
30247
32187
|
});
|
|
30248
32188
|
console.log(`Installed skill "${name}" (${fileCount} file${fileCount === 1 ? "" : "s"}) \u2192 ${relDirs.join(", ")}`);
|
|
30249
32189
|
console.log("Reload your agent (e.g. restart Claude Code) to pick up the skill.");
|
|
@@ -32836,12 +34776,12 @@ var init_providers = __esm({
|
|
|
32836
34776
|
// src/data/commands/report.ts
|
|
32837
34777
|
import { Command as Command6 } from "commander";
|
|
32838
34778
|
import { readFileSync as readFileSync20 } from "fs";
|
|
32839
|
-
import { dirname as
|
|
34779
|
+
import { dirname as dirname15, join as join36 } from "path";
|
|
32840
34780
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
32841
34781
|
function resolveCliVersion() {
|
|
32842
34782
|
for (const candidate of [
|
|
32843
|
-
|
|
32844
|
-
|
|
34783
|
+
join36(here, "..", "package.json"),
|
|
34784
|
+
join36(here, "..", "..", "..", "package.json")
|
|
32845
34785
|
]) {
|
|
32846
34786
|
try {
|
|
32847
34787
|
const version = JSON.parse(readFileSync20(candidate, "utf-8")).version;
|
|
@@ -33040,7 +34980,7 @@ var init_report3 = __esm({
|
|
|
33040
34980
|
init_terminal_output();
|
|
33041
34981
|
init_workspace();
|
|
33042
34982
|
init_skill_version();
|
|
33043
|
-
here =
|
|
34983
|
+
here = dirname15(fileURLToPath2(import.meta.url));
|
|
33044
34984
|
}
|
|
33045
34985
|
});
|
|
33046
34986
|
|
|
@@ -33353,16 +35293,16 @@ MCP URL: ${toolsetMcpUrl(slug)}`);
|
|
|
33353
35293
|
).option("--force", "Revoke even if the token appears to be in live use").action(async function(tokenId, opts) {
|
|
33354
35294
|
const client = await createDataClient();
|
|
33355
35295
|
const id = pathSegment(tokenId, "token id");
|
|
33356
|
-
const
|
|
35296
|
+
const path41 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
|
|
33357
35297
|
const format = outputFormat(this);
|
|
33358
35298
|
const revoked = (forced) => printOutput({ token_id: tokenId, revoked: true, forced }, format);
|
|
33359
35299
|
if (opts.force) {
|
|
33360
|
-
await client.request("DELETE",
|
|
35300
|
+
await client.request("DELETE", path41(true));
|
|
33361
35301
|
revoked(true);
|
|
33362
35302
|
return;
|
|
33363
35303
|
}
|
|
33364
35304
|
try {
|
|
33365
|
-
await client.request("DELETE",
|
|
35305
|
+
await client.request("DELETE", path41(false));
|
|
33366
35306
|
revoked(false);
|
|
33367
35307
|
} catch (err) {
|
|
33368
35308
|
if (!(err instanceof ApiError) || err.status !== 409 || dataErrorDetails(err)?.requires_force !== true) {
|
|
@@ -33377,7 +35317,7 @@ MCP URL: ${toolsetMcpUrl(slug)}`);
|
|
|
33377
35317
|
console.error("Aborted.");
|
|
33378
35318
|
return process.exit(1);
|
|
33379
35319
|
}
|
|
33380
|
-
await client.request("DELETE",
|
|
35320
|
+
await client.request("DELETE", path41(true));
|
|
33381
35321
|
revoked(true);
|
|
33382
35322
|
}
|
|
33383
35323
|
});
|
|
@@ -33525,9 +35465,9 @@ var init_base_tags = __esm({
|
|
|
33525
35465
|
|
|
33526
35466
|
// src/data/commands/bases.ts
|
|
33527
35467
|
import { Command as Command10 } from "commander";
|
|
33528
|
-
function pageOf(
|
|
33529
|
-
if (!cursor) return
|
|
33530
|
-
return `${
|
|
35468
|
+
function pageOf(path41, cursor) {
|
|
35469
|
+
if (!cursor) return path41;
|
|
35470
|
+
return `${path41}${path41.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
|
|
33531
35471
|
}
|
|
33532
35472
|
function parseEnum(flag, value, allowed) {
|
|
33533
35473
|
if (value === void 0) return void 0;
|
|
@@ -33684,9 +35624,9 @@ function buildBasesCommand() {
|
|
|
33684
35624
|
});
|
|
33685
35625
|
bases.command("list-previews <origin-id>").description("List preview bases cloned from a base (production or preview)").action(async function(originId) {
|
|
33686
35626
|
const client = await createDataClient();
|
|
33687
|
-
const
|
|
35627
|
+
const path41 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
|
|
33688
35628
|
printOutput(
|
|
33689
|
-
await client.collectPages((cursor) => pageOf(
|
|
35629
|
+
await client.collectPages((cursor) => pageOf(path41, cursor)),
|
|
33690
35630
|
outputFormat(this)
|
|
33691
35631
|
);
|
|
33692
35632
|
});
|
|
@@ -33733,9 +35673,9 @@ function buildBasesCommand() {
|
|
|
33733
35673
|
});
|
|
33734
35674
|
bases.command("promotions <production-id>").description("List promotion history for a production base").action(async function(productionId) {
|
|
33735
35675
|
const client = await createDataClient();
|
|
33736
|
-
const
|
|
35676
|
+
const path41 = `/v1/${pathSegment(productionId, "production base id")}/promotions`;
|
|
33737
35677
|
printOutput(
|
|
33738
|
-
await client.collectPages((cursor) => pageOf(
|
|
35678
|
+
await client.collectPages((cursor) => pageOf(path41, cursor)),
|
|
33739
35679
|
outputFormat(this)
|
|
33740
35680
|
);
|
|
33741
35681
|
});
|
|
@@ -33874,7 +35814,7 @@ var init_file_types = __esm({
|
|
|
33874
35814
|
// src/data/commands/files.ts
|
|
33875
35815
|
import { Command as Command12 } from "commander";
|
|
33876
35816
|
import { readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
|
|
33877
|
-
import { basename as
|
|
35817
|
+
import { basename as basename25 } from "path";
|
|
33878
35818
|
function renderFileDiff(fileType, filePath, from, to, d) {
|
|
33879
35819
|
console.log(sanitizeTerminalText(`${fileType}/${filePath}: v${from} \u2192 v${to}`));
|
|
33880
35820
|
const md = d.metadata_delta;
|
|
@@ -33906,7 +35846,7 @@ function renderFileDiff(fileType, filePath, from, to, d) {
|
|
|
33906
35846
|
}
|
|
33907
35847
|
function downloadTarget(remotePath, to) {
|
|
33908
35848
|
if (to) return to;
|
|
33909
|
-
const derived =
|
|
35849
|
+
const derived = basename25(remotePath);
|
|
33910
35850
|
if (derived === "" || derived === "." || derived === "..") {
|
|
33911
35851
|
throw expected(
|
|
33912
35852
|
`Cannot derive a local filename from "${remotePath}" \u2014 pass --to <local> to name it.`
|
|
@@ -34282,8 +36222,8 @@ function buildRecordsCommand() {
|
|
|
34282
36222
|
const body = { data: parseData(opts.data) };
|
|
34283
36223
|
if (opts.externalId) body.external_id = opts.externalId;
|
|
34284
36224
|
if (opts.externalSource) body.external_source = opts.externalSource;
|
|
34285
|
-
const
|
|
34286
|
-
printOutput(await client.request("PUT",
|
|
36225
|
+
const path41 = opts.id ? `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(opts.id, "--id")}` : `/v1/${base}/records/${pathSegment(recordType2, "record_type")}`;
|
|
36226
|
+
printOutput(await client.request("PUT", path41, body), outputFormat(this));
|
|
34287
36227
|
});
|
|
34288
36228
|
records.command("query <record_type>").description("List/search records: exact filters + fuzzy `search`, sorting, pagination").option(
|
|
34289
36229
|
"--external-source <source>",
|
|
@@ -34318,9 +36258,9 @@ function buildRecordsCommand() {
|
|
|
34318
36258
|
).action(async function(recordType2, id, opts) {
|
|
34319
36259
|
const base = pathSegment(requireBase(this), "--base");
|
|
34320
36260
|
const client = await createDataClient();
|
|
34321
|
-
let
|
|
34322
|
-
if (opts.externalSource)
|
|
34323
|
-
printOutput(await client.request("GET",
|
|
36261
|
+
let path41 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
|
|
36262
|
+
if (opts.externalSource) path41 += `?external_source=${encodeURIComponent(opts.externalSource)}`;
|
|
36263
|
+
printOutput(await client.request("GET", path41), outputFormat(this));
|
|
34324
36264
|
});
|
|
34325
36265
|
records.command("delete <record_type> <id>").description(
|
|
34326
36266
|
"Delete a record by internal ID, or by external_id (pass --external-source to scope it)"
|
|
@@ -34334,11 +36274,11 @@ function buildRecordsCommand() {
|
|
|
34334
36274
|
}
|
|
34335
36275
|
const base = pathSegment(requireBase(this), "--base");
|
|
34336
36276
|
const client = await createDataClient();
|
|
34337
|
-
let
|
|
36277
|
+
let path41 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
|
|
34338
36278
|
if (opts.externalSource) {
|
|
34339
|
-
|
|
36279
|
+
path41 += `?external_source=${encodeURIComponent(opts.externalSource)}&external_id=${encodeURIComponent(id)}`;
|
|
34340
36280
|
}
|
|
34341
|
-
await client.request("DELETE",
|
|
36281
|
+
await client.request("DELETE", path41);
|
|
34342
36282
|
console.log("Deleted");
|
|
34343
36283
|
});
|
|
34344
36284
|
records.command("history <id>").description(
|
|
@@ -34503,8 +36443,8 @@ function buildRelationshipsCommand() {
|
|
|
34503
36443
|
if (opts.data) body.data = parseData(opts.data);
|
|
34504
36444
|
const base = pathSegment(requireBase(this), "--base");
|
|
34505
36445
|
const client = await createDataClient();
|
|
34506
|
-
const
|
|
34507
|
-
printOutput(await client.request("PUT",
|
|
36446
|
+
const path41 = opts.id ? `/v1/${base}/relationships/${pathSegment(opts.id, "--id")}` : `/v1/${base}/relationships`;
|
|
36447
|
+
printOutput(await client.request("PUT", path41, body), outputFormat(this));
|
|
34508
36448
|
});
|
|
34509
36449
|
relationships.command("get <id>").description(
|
|
34510
36450
|
"Get a relationship by ID (or by its external key: pass the external_id with --rel-type)"
|
|
@@ -34737,8 +36677,8 @@ function buildToolsetsCommand() {
|
|
|
34737
36677
|
toolsets.command("get <slug>").description("Get a toolset").option("--resolved", "Include resolved record_type schemas").action(async function(slug, opts) {
|
|
34738
36678
|
const base = pathSegment(requireBase(this), "--base");
|
|
34739
36679
|
const client = await createDataClient();
|
|
34740
|
-
const
|
|
34741
|
-
printOutput(await client.request("GET",
|
|
36680
|
+
const path41 = opts.resolved ? `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}/resolved` : `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`;
|
|
36681
|
+
printOutput(await client.request("GET", path41), outputFormat(this));
|
|
34742
36682
|
});
|
|
34743
36683
|
toolsets.command("list").description("List all toolsets").action(async function() {
|
|
34744
36684
|
const base = pathSegment(requireBase(this), "--base");
|
|
@@ -34973,7 +36913,7 @@ init_utils();
|
|
|
34973
36913
|
init_registry();
|
|
34974
36914
|
import { readFileSync as readFileSync24 } from "fs";
|
|
34975
36915
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
34976
|
-
import { dirname as
|
|
36916
|
+
import { dirname as dirname16, join as join37 } from "path";
|
|
34977
36917
|
|
|
34978
36918
|
// src/lib/version-refresh.ts
|
|
34979
36919
|
init_version_cache();
|
|
@@ -35128,8 +37068,8 @@ Run \`wayai admin skill install\` to update.`);
|
|
|
35128
37068
|
}
|
|
35129
37069
|
|
|
35130
37070
|
// src/index.ts
|
|
35131
|
-
var __dirname =
|
|
35132
|
-
var pkg = JSON.parse(readFileSync24(
|
|
37071
|
+
var __dirname = dirname16(fileURLToPath3(import.meta.url));
|
|
37072
|
+
var pkg = JSON.parse(readFileSync24(join37(__dirname, "..", "package.json"), "utf-8"));
|
|
35133
37073
|
var [, , command, ...args] = process.argv;
|
|
35134
37074
|
var isBackgroundRefresh = command === REFRESH_COMMAND;
|
|
35135
37075
|
if (!isBackgroundRefresh) initSentry(command, pkg.version);
|