@method-ai-hq/sdk 0.0.0-stage → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +23 -2
- package/dist/packages/contracts/src/identity.d.ts +16 -0
- package/dist/packages/contracts/src/identity.js +41 -0
- package/dist/packages/contracts/src/method-package.d.ts +71 -0
- package/dist/packages/contracts/src/method-package.js +22 -0
- package/dist/packages/method-document/src/conversion-report.d.ts +138 -0
- package/dist/packages/method-document/src/conversion-report.js +23 -0
- package/dist/packages/sdk/examples/daily-briefing/README.md +31 -0
- package/dist/packages/sdk/examples/daily-briefing/TASK.md +3 -0
- package/dist/packages/sdk/examples/daily-briefing/approved-report.md +138 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_artifacts.py +39 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_check_inputs.py +47 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_files.py +31 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_manifest.py +23 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_markdown.py +67 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_progress.py +18 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_render.py +71 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_sessions.py +37 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_times.py +158 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_validation.py +101 -0
- package/dist/packages/sdk/examples/daily-briefing/briefing_website.py +215 -0
- package/dist/packages/sdk/examples/daily-briefing/checks.json +19 -0
- package/dist/packages/sdk/examples/daily-briefing/daily-briefing.method +158 -0
- package/dist/packages/sdk/examples/daily-briefing/inputs.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/package-lock.json +10 -0
- package/dist/packages/sdk/examples/daily-briefing/package.json +4 -0
- package/dist/packages/sdk/examples/daily-briefing/pyproject.toml +8 -0
- package/dist/packages/sdk/examples/daily-briefing/reader/reader.css +35 -0
- package/dist/packages/sdk/examples/daily-briefing/reader/reader.js +42 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/context.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/documents/document-001.md +219 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/media.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/metadata/documents.json +39 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0001.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0002.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0003.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0004.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0005.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0006.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0007.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0008.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0009.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0010.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0011.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0012.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0013.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0014.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0015.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0016.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0017.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0018.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0019.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0020.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0021.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0022.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0023.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0024.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0025.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0026.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0027.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0028.json +49 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/sessions.json +1 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/timeline.jsonl +28 -0
- package/dist/packages/sdk/examples/daily-briefing/sample/untimed.jsonl +0 -0
- package/dist/packages/sdk/examples/daily-briefing/sample-output.zip +0 -0
- package/dist/packages/sdk/examples/daily-briefing/sample-report.md +51 -0
- package/dist/packages/sdk/examples/daily-briefing/uv.lock +128 -0
- package/dist/packages/sdk/examples/daily-briefing/vendor/marked-LICENSE.md +44 -0
- package/dist/packages/sdk/examples/daily-briefing/vendor/marked.mjs +77 -0
- package/dist/packages/sdk/examples/files.json +126 -0
- package/dist/packages/sdk/examples/message-routing/README.md +23 -0
- package/dist/packages/sdk/examples/message-routing/TASK.md +5 -0
- package/dist/packages/sdk/examples/message-routing/choose-destination.mjs +10 -0
- package/dist/packages/sdk/examples/message-routing/create-ticket.mjs +15 -0
- package/dist/packages/sdk/examples/message-routing/inputs.json +1 -0
- package/dist/packages/sdk/examples/message-routing/message-routing.method +45 -0
- package/dist/packages/sdk/examples/message-routing/result.fixture.json +6 -0
- package/dist/packages/sdk/examples/message-routing/routing.mjs +29 -0
- package/dist/packages/sdk/examples/message-routing/routing.test.mjs +27 -0
- package/dist/packages/sdk/examples/message-routing/ticket.method +88 -0
- package/dist/packages/sdk/examples/message-routing/ticket.test.mjs +64 -0
- package/dist/packages/sdk/examples/notes-summary/README.md +23 -0
- package/dist/packages/sdk/examples/notes-summary/TASK.md +3 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/case.json +36 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/examples.json +16 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/files/0 +10 -0
- package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/recording.json +195 -0
- package/dist/packages/sdk/examples/notes-summary/notes/2026-10-05-standup.md +4 -0
- package/dist/packages/sdk/examples/notes-summary/notes/2026-10-07-planning.md +3 -0
- package/dist/packages/sdk/examples/notes-summary/notes-summary.method +54 -0
- package/dist/packages/sdk/examples/notes-summary/out/.keep +0 -0
- package/dist/packages/sdk/examples/notes-summary/read-notes.mjs +4 -0
- package/dist/packages/sdk/examples/notes-summary/save.mjs +7 -0
- package/dist/packages/sdk/examples/outbound-management/README.md +119 -0
- package/dist/packages/sdk/examples/outbound-management/TASK.md +5 -0
- package/dist/packages/sdk/examples/outbound-management/check_plan.py +16 -0
- package/dist/packages/sdk/examples/outbound-management/check_sources.py +14 -0
- package/dist/packages/sdk/examples/outbound-management/crm.py +153 -0
- package/dist/packages/sdk/examples/outbound-management/crm_test.py +141 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/README.md +15 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/crm.json +54 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/day-1/messages.json +29 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/day-2/messages.json +38 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/observations.json +12 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/plan.json +69 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/receipt.json +1 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/state.json +99 -0
- package/dist/packages/sdk/examples/outbound-management/fixtures/tasks.md +16 -0
- package/dist/packages/sdk/examples/outbound-management/inputs.json +4 -0
- package/dist/packages/sdk/examples/outbound-management/outbound.method +277 -0
- package/dist/packages/sdk/examples/outbound-management/read_crm.py +4 -0
- package/dist/packages/sdk/examples/outbound-management/save_day.py +4 -0
- package/dist/packages/sdk/examples/outbound-management/starter/crm.json +11 -0
- package/dist/packages/sdk/examples/social-briefing/README.md +41 -0
- package/dist/packages/sdk/examples/social-briefing/TASK.md +5 -0
- package/dist/packages/sdk/examples/social-briefing/inputs.json +5 -0
- package/dist/packages/sdk/examples/social-briefing/result.fixture.json +14 -0
- package/dist/packages/sdk/examples/social-briefing/social-briefing.method +110 -0
- package/dist/packages/sdk/examples/support-triage/README.md +77 -0
- package/dist/packages/sdk/examples/support-triage/TASK.md +3 -0
- package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/case.json +40 -0
- package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/recording.json +460 -0
- package/dist/packages/sdk/examples/support-triage/fake-helpdesk.mjs +38 -0
- package/dist/packages/sdk/examples/support-triage/helpdesk.mjs +33 -0
- package/dist/packages/sdk/examples/support-triage/inputs.json +1 -0
- package/dist/packages/sdk/examples/support-triage/read-ticket.mjs +8 -0
- package/dist/packages/sdk/examples/support-triage/route.mjs +4 -0
- package/dist/packages/sdk/examples/support-triage/sample-tickets.json +5 -0
- package/dist/packages/sdk/examples/support-triage/set-ticket.mjs +9 -0
- package/dist/packages/sdk/examples/support-triage/support-triage.method +165 -0
- package/dist/packages/sdk/examples/support-triage/triage.mjs +38 -0
- package/dist/packages/sdk/examples/support-triage/triage.test.mjs +68 -0
- package/dist/packages/sdk/observers/mail.delivery/fetch.mjs +87 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce-by-recipient.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/delayed.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/delivered.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/empty.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/fixtures/other-message.json +1 -0
- package/dist/packages/sdk/observers/mail.delivery/judge.mjs +13 -0
- package/dist/packages/sdk/observers/mail.delivery/observer.json +14 -0
- package/dist/packages/sdk/package.json +232 -0
- package/dist/packages/sdk/src/authoring-example.d.ts +91 -0
- package/dist/packages/sdk/src/authoring-example.js +50 -0
- package/dist/packages/sdk/src/authoring-instructions.d.ts +20 -0
- package/dist/packages/sdk/src/authoring-instructions.js +151 -0
- package/dist/packages/sdk/src/authoring-prompt.d.ts +2 -0
- package/dist/packages/sdk/src/authoring-prompt.js +8 -0
- package/dist/packages/sdk/src/authoring.d.ts +10 -0
- package/dist/packages/sdk/src/authoring.js +287 -0
- package/dist/packages/sdk/src/bindings.d.ts +4 -0
- package/dist/packages/sdk/src/bindings.js +124 -0
- package/dist/packages/sdk/src/bridge.d.ts +2 -0
- package/dist/packages/sdk/src/bridge.js +23 -0
- package/dist/packages/sdk/src/browser-runtime/pyproject.toml +5 -0
- package/dist/packages/sdk/src/browser-runtime/tools.json +189 -0
- package/dist/packages/sdk/src/browser-runtime/uv.lock +3894 -0
- package/dist/packages/sdk/src/browser-service.py +127 -0
- package/dist/packages/sdk/src/browser.d.ts +38 -0
- package/dist/packages/sdk/src/browser.js +203 -0
- package/dist/packages/sdk/src/capabilities.d.ts +13 -0
- package/dist/packages/sdk/src/capabilities.js +68 -0
- package/dist/packages/sdk/src/classification-client.d.ts +4 -0
- package/dist/packages/sdk/src/classification-client.js +27 -0
- package/dist/packages/sdk/src/cloud.d.ts +23 -0
- package/dist/packages/sdk/src/cloud.js +43 -0
- package/dist/packages/sdk/src/computer-settings.d.ts +28 -0
- package/dist/packages/sdk/src/computer-settings.js +57 -0
- package/dist/packages/sdk/src/connect.d.ts +47 -0
- package/dist/packages/sdk/src/connect.js +203 -0
- package/dist/packages/sdk/src/current-inspection.d.ts +3 -0
- package/dist/packages/sdk/src/current-inspection.js +146 -0
- package/dist/packages/sdk/src/current-runtime.d.ts +14 -0
- package/dist/packages/sdk/src/current-runtime.js +167 -0
- package/dist/packages/sdk/src/doctor.d.ts +17 -0
- package/dist/packages/sdk/src/doctor.js +68 -0
- package/dist/packages/sdk/src/executable.d.ts +2 -0
- package/dist/packages/sdk/src/executable.js +22 -0
- package/dist/packages/sdk/src/explain-effects.d.ts +32 -0
- package/dist/packages/sdk/src/explain-effects.js +192 -0
- package/dist/packages/sdk/src/explain.d.ts +115 -0
- package/dist/packages/sdk/src/explain.js +441 -0
- package/dist/packages/sdk/src/files.d.ts +9 -0
- package/dist/packages/sdk/src/files.js +50 -0
- package/dist/packages/sdk/src/guard-keys.d.ts +5 -0
- package/dist/packages/sdk/src/guard-keys.js +42 -0
- package/dist/packages/sdk/src/hosted-models.d.ts +49 -0
- package/dist/packages/sdk/src/hosted-models.js +81 -0
- package/dist/packages/sdk/src/improve.d.ts +55 -0
- package/dist/packages/sdk/src/improve.js +159 -0
- package/dist/packages/sdk/src/index.d.ts +13 -0
- package/dist/packages/sdk/src/index.js +13 -0
- package/dist/packages/sdk/src/inspect.d.ts +6 -0
- package/dist/packages/sdk/src/inspect.js +17 -0
- package/dist/packages/sdk/src/link.d.ts +12 -0
- package/dist/packages/sdk/src/link.js +53 -0
- package/dist/packages/sdk/src/local-cli.d.ts +24 -0
- package/dist/packages/sdk/src/local-cli.js +146 -0
- package/dist/packages/sdk/src/local-setup.d.ts +15 -0
- package/dist/packages/sdk/src/local-setup.js +42 -0
- package/dist/packages/sdk/src/method-client.d.ts +30 -0
- package/dist/packages/sdk/src/method-client.js +250 -0
- package/dist/packages/sdk/src/method-files.d.ts +29 -0
- package/dist/packages/sdk/src/method-files.js +93 -0
- package/dist/packages/sdk/src/method-help.d.ts +19 -0
- package/dist/packages/sdk/src/method-help.js +404 -0
- package/dist/packages/sdk/src/method-issues.d.ts +86 -0
- package/dist/packages/sdk/src/method-issues.js +192 -0
- package/dist/packages/sdk/src/method-sync.d.ts +46 -0
- package/dist/packages/sdk/src/method-sync.js +293 -0
- package/dist/packages/sdk/src/method.d.ts +5 -0
- package/dist/packages/sdk/src/method.js +359 -0
- package/dist/packages/sdk/src/model-checks.d.ts +96 -0
- package/dist/packages/sdk/src/model-checks.js +181 -0
- package/dist/packages/sdk/src/outbox.d.ts +120 -0
- package/dist/packages/sdk/src/outbox.js +297 -0
- package/dist/packages/sdk/src/own-model-key.d.ts +22 -0
- package/dist/packages/sdk/src/own-model-key.js +76 -0
- package/dist/packages/sdk/src/prepare.d.ts +24 -0
- package/dist/packages/sdk/src/prepare.js +245 -0
- package/dist/packages/sdk/src/process.d.ts +15 -0
- package/dist/packages/sdk/src/process.js +107 -0
- package/dist/packages/sdk/src/production.d.ts +182 -0
- package/dist/packages/sdk/src/production.js +284 -0
- package/dist/packages/sdk/src/progress.d.ts +12 -0
- package/dist/packages/sdk/src/progress.js +48 -0
- package/dist/packages/sdk/src/proposals.d.ts +73 -0
- package/dist/packages/sdk/src/proposals.js +284 -0
- package/dist/packages/sdk/src/quality.d.ts +82 -0
- package/dist/packages/sdk/src/quality.js +294 -0
- package/dist/packages/sdk/src/result-files.d.ts +5 -0
- package/dist/packages/sdk/src/result-files.js +149 -0
- package/dist/packages/sdk/src/result-transfer.d.ts +21 -0
- package/dist/packages/sdk/src/result-transfer.js +37 -0
- package/dist/packages/sdk/src/run-data.d.ts +11 -0
- package/dist/packages/sdk/src/run-data.js +80 -0
- package/dist/packages/sdk/src/run-method.d.ts +2 -0
- package/dist/packages/sdk/src/run-method.js +2 -0
- package/dist/packages/sdk/src/run-saved.d.ts +5 -0
- package/dist/packages/sdk/src/run-saved.js +138 -0
- package/dist/packages/sdk/src/run-worker.d.ts +6 -0
- package/dist/packages/sdk/src/run-worker.js +131 -0
- package/dist/packages/sdk/src/secrets.d.ts +37 -0
- package/dist/packages/sdk/src/secrets.js +196 -0
- package/dist/packages/sdk/src/update.d.ts +5 -0
- package/dist/packages/sdk/src/update.js +39 -0
- package/dist/packages/sdk/src/versions.d.ts +58 -0
- package/dist/packages/sdk/src/versions.js +144 -0
- package/dist/packages/sdk/src/worker.d.ts +120 -0
- package/dist/packages/sdk/src/worker.js +443 -0
- package/dist/packages/workflow-language/src/execution.d.ts +1 -0
- package/dist/packages/workflow-language/src/execution.js +1 -0
- package/dist/packages/workflow-language/src/inspection.d.ts +990 -0
- package/dist/packages/workflow-language/src/inspection.js +130 -0
- package/dist/packages/workflow-language/src/result-files.d.ts +160 -0
- package/dist/packages/workflow-language/src/result-files.js +67 -0
- package/dist/packages/workflow-language/src/schema.d.ts +207 -0
- package/dist/packages/workflow-language/src/schema.js +56 -0
- package/dist/packages/workflow-language/src/validate.d.ts +11 -0
- package/dist/packages/workflow-language/src/validate.js +77 -0
- package/package.json +230 -4
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The issues of a local Method: the runtime's methodIssues (format and code checks, with this computer's secret
|
|
3
|
+
* values and the files that will be uploaded), the model privacy check, and the model checks. Only errors block.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, openSync, closeSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { methodIssues } from '@withmethod/runtime/document.js';
|
|
11
|
+
import { isModelId } from '@withmethod/runtime/semantics.js';
|
|
12
|
+
import { parseDocumentValue } from '../../workflow-language/src/validate.js';
|
|
13
|
+
import { authoringPath } from './authoring.js';
|
|
14
|
+
import { changedSteps, checkTargets, checks, checksDir, evaluateTarget, modelIssues, runChecks, shownChecks } from './model-checks.js';
|
|
15
|
+
import { packageFileNames } from './method-files.js';
|
|
16
|
+
import { MethodClient } from './method-client.js';
|
|
17
|
+
/** A .env file holds secret values: issue checks never read it. */
|
|
18
|
+
export const refused = (path) => /^\.env(\.|$)/.test(basename(path));
|
|
19
|
+
/** A file of the Method folder as text; undefined when it is missing, outside the folder, or not allowed. */
|
|
20
|
+
export function folderReader(file) {
|
|
21
|
+
const root = realpathSync(dirname(resolve(file)));
|
|
22
|
+
return (path) => {
|
|
23
|
+
try {
|
|
24
|
+
const target = realpathSync(resolve(root, path));
|
|
25
|
+
if (!target.startsWith(root + sep) || refused(target))
|
|
26
|
+
return undefined;
|
|
27
|
+
return readFileSync(target, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function folderBytes(file, names) {
|
|
35
|
+
const root = realpathSync(dirname(resolve(file))), files = {};
|
|
36
|
+
for (const name of names) {
|
|
37
|
+
try {
|
|
38
|
+
const target = realpathSync(resolve(root, name));
|
|
39
|
+
if (target.startsWith(root + sep) && !refused(target))
|
|
40
|
+
files[name] = readFileSync(target);
|
|
41
|
+
}
|
|
42
|
+
catch { /* A missing file is reported by preflight. */ }
|
|
43
|
+
}
|
|
44
|
+
return files;
|
|
45
|
+
}
|
|
46
|
+
/** The hosted model ID that each call or agent step (or agent check) names, with the field. */
|
|
47
|
+
export function hostedModelUses(method) {
|
|
48
|
+
const uses = [];
|
|
49
|
+
for (const [id, step] of Object.entries(method.steps ?? {}))
|
|
50
|
+
for (const [field, exec] of [['do.model', step.do], ['check.model', step.check]]) {
|
|
51
|
+
if (!exec || !['call', 'agent'].includes(exec.kind) || typeof exec.model !== 'string')
|
|
52
|
+
continue;
|
|
53
|
+
const named = method.models?.[exec.model];
|
|
54
|
+
// A local-agent entry ({agent: codex | claude}) is not a hosted model.
|
|
55
|
+
if (named?.agent)
|
|
56
|
+
continue;
|
|
57
|
+
const model = typeof named === 'string' ? named : typeof named?.model === 'string' ? named.model : isModelId(exec.model) ? exec.model : undefined;
|
|
58
|
+
if (model)
|
|
59
|
+
uses.push({ step: id, field, model, ...(named ? { name: exec.model } : {}) });
|
|
60
|
+
}
|
|
61
|
+
return uses;
|
|
62
|
+
}
|
|
63
|
+
/** model_not_private: a step names a hosted model that has no private endpoint. Deterministic from the saved list. */
|
|
64
|
+
export function privacyIssues(method, privateIds) {
|
|
65
|
+
return hostedModelUses(method).filter(use => !privateIds.has(use.model)).map(use => ({
|
|
66
|
+
code: 'model_not_private', level: 'warning', step: use.step, field: use.field,
|
|
67
|
+
message: `${use.step} uses ${use.model}${use.name ? ` (models.${use.name})` : ''}, which no provider serves with the private options (no training, zero data retention), so hosted runs refuse it.`,
|
|
68
|
+
fix: 'Choose a model from method models.',
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* legacy_runtime_json and legacy_sidecar: an old runtime.json or FILE.method.json is next to the Method. Method files
|
|
73
|
+
* no longer read them, so a run would lose their settings. The fix says what to move where; nothing is changed here.
|
|
74
|
+
*/
|
|
75
|
+
export function legacyIssues(file) {
|
|
76
|
+
const path = authoringPath(file), folder = dirname(path), issues = [];
|
|
77
|
+
if (existsSync(join(folder, 'runtime.json')))
|
|
78
|
+
issues.push({ code: 'legacy_runtime_json', level: 'error',
|
|
79
|
+
message: `runtime.json is next to ${basename(path)}. Methods no longer read runtime.json, so its settings would be lost.`,
|
|
80
|
+
fix: 'Move each part into the Method, then delete runtime.json. models -> top-level models: (NAME: provider/model, or {model, max_output_tokens, reasoning_effort}); a profile with backend codex or claude -> NAME: {agent: codex|claude, model, reasoning_effort} (keep its model and reasoning_effort). limits -> top-level limits:. step_defaults -> limits.step. tools -> top-level tools:. environment paths -> method bind ID NAME --file FOLDER (a folder beside the Method with the connection name needs nothing). classification.api_key_env -> method config model-key. runtimes and allow_local_processes need nothing. Set format: method/3.4 and remove runtime.json from files:.' });
|
|
81
|
+
if (existsSync(`${path}.method.json`))
|
|
82
|
+
issues.push({ code: 'legacy_sidecar', level: 'error',
|
|
83
|
+
message: `${basename(path)}.method.json is next to ${basename(path)}. Methods keep their ID in the file now.`,
|
|
84
|
+
fix: `Put its workflow_id as the line id: wf_... directly after format: method/3.4 in ${basename(path)}, then delete ${basename(path)}.method.json.` });
|
|
85
|
+
return issues;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* All issues of a Method file. With changed, model checks run for the steps whose text changed since the last
|
|
89
|
+
* validate (in parallel, waitMs in all; the rest finish in the background). Without it, only cached answers are used.
|
|
90
|
+
*/
|
|
91
|
+
export async function collectIssues(file, options = {}) {
|
|
92
|
+
const path = authoringPath(file), text = readFileSync(path, 'utf8');
|
|
93
|
+
const legacy = legacyIssues(path), first = [...legacy, ...methodIssues(text)];
|
|
94
|
+
if (first.some(issue => issue.level === 'error'))
|
|
95
|
+
return { issues: first, pending: 0, notChecked: [], invalid: true };
|
|
96
|
+
const method = parseDocumentValue(text), read = folderReader(path);
|
|
97
|
+
const { deviceSecretValues, resolveSecrets } = await import('./secrets.js');
|
|
98
|
+
const declared = Object.keys(method.secrets ?? {});
|
|
99
|
+
const client = options.client ?? new MethodClient();
|
|
100
|
+
const signedIn = (() => { try {
|
|
101
|
+
return !!client.token();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
} })();
|
|
106
|
+
const changed = options.phase === 'publish' ? undefined : changedSteps(path, method, read);
|
|
107
|
+
const targets = checkTargets(method, read);
|
|
108
|
+
const provider = signedIn ? (await import('./classification-client.js')).managedClassification(client) : undefined;
|
|
109
|
+
const run = changed ? targets.filter(target => changed.has(target.step)) : [];
|
|
110
|
+
const [{ done, pending }, list] = await Promise.all([
|
|
111
|
+
runChecks(provider, run, options.waitMs ?? 3000),
|
|
112
|
+
signedIn ? (await import('./hosted-models.js')).privateModels(client, { waitMs: options.waitMs ?? 3000 }).catch(() => undefined) : Promise.resolve(undefined),
|
|
113
|
+
]);
|
|
114
|
+
// Unchanged steps use the answers saved earlier; a missing one is not computed now.
|
|
115
|
+
const { cached } = await import('./model-checks.js');
|
|
116
|
+
const results = [...done, ...targets.filter(target => !run.includes(target)).flatMap(target => { const hit = cached(target.key); return hit ? [hit] : []; })];
|
|
117
|
+
if (pending.length && provider && options.background !== false)
|
|
118
|
+
finishInBackground(pending);
|
|
119
|
+
const computed = new Set(results.map(result => `${result.check} ${result.step}`));
|
|
120
|
+
// Shadow checks and checks without an answer give no accept_unused note.
|
|
121
|
+
const notChecked = [...Object.keys(checks).filter(code => !shownChecks.includes(code) || targets.some(target => target.check === code && !computed.has(`${code} ${target.step}`))),
|
|
122
|
+
...(list ? [] : ['model_not_private'])];
|
|
123
|
+
const issues = methodIssues(text, {
|
|
124
|
+
secretValues: deviceSecretValues(declared), availableSecrets: Object.keys(resolveSecrets(declared)), phase: 'validate',
|
|
125
|
+
files: folderBytes(path, packageFileNames(dirname(path), method)),
|
|
126
|
+
issues: [...modelIssues(results), ...(list ? privacyIssues(method, new Set(list.models.map(model => model.id))) : [])],
|
|
127
|
+
notChecked,
|
|
128
|
+
});
|
|
129
|
+
return { issues, ...(changed ? { changed } : {}), pending: provider ? pending.length : 0, notChecked };
|
|
130
|
+
}
|
|
131
|
+
/** Finish the checks that did not answer in time in a detached process, so the next validate has their answers. */
|
|
132
|
+
function finishInBackground(targets) {
|
|
133
|
+
try {
|
|
134
|
+
if (process.env.METHOD_NO_BACKGROUND_CHECKS === '1')
|
|
135
|
+
return;
|
|
136
|
+
const dir = join(checksDir(), 'pending');
|
|
137
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
138
|
+
const file = join(dir, `${randomUUID()}.json`);
|
|
139
|
+
writeFileSync(file, JSON.stringify(targets), { mode: 0o600 });
|
|
140
|
+
const suffix = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
|
|
141
|
+
const entry = fileURLToPath(new URL(`./method${suffix}`, import.meta.url));
|
|
142
|
+
const log = openSync(join(dir, 'checks.log'), 'a', 0o600);
|
|
143
|
+
const child = spawn(process.execPath, [...process.execArgv, entry, '__checks', file], { detached: true, stdio: ['ignore', log, log], env: { ...process.env, METHOD_RUN_WORKER: '' } });
|
|
144
|
+
closeSync(log);
|
|
145
|
+
child.on('error', () => { });
|
|
146
|
+
child.unref();
|
|
147
|
+
}
|
|
148
|
+
catch { /* A check never delays or fails validate. */ }
|
|
149
|
+
}
|
|
150
|
+
/** The background worker: answer each pending target and save it in the cache. */
|
|
151
|
+
export async function checksWorker(file) {
|
|
152
|
+
const targets = JSON.parse(readFileSync(file, 'utf8'));
|
|
153
|
+
const { rmSync } = await import('node:fs');
|
|
154
|
+
rmSync(file, { force: true });
|
|
155
|
+
const provider = (await import('./classification-client.js')).managedClassification(new MethodClient());
|
|
156
|
+
await Promise.allSettled(targets.map(target => evaluateTarget(provider, target, AbortSignal.timeout(120_000))));
|
|
157
|
+
}
|
|
158
|
+
const where = (issue) => [issue.file ?? (issue.step ? `steps.${issue.step}${issue.field ? `.${issue.field}` : ''}` : issue.field), issue.line ? `line ${issue.line}` : undefined].filter(Boolean).join(', ');
|
|
159
|
+
/** One issue as the coding agent reads it: what is wrong, what to do, and, for a warning on a step, how to accept it. */
|
|
160
|
+
export function formatIssue(issue) {
|
|
161
|
+
return { level: issue.level, code: issue.code, ...(where(issue) ? { at: where(issue) } : {}), message: issue.message, fix: issue.fix,
|
|
162
|
+
...(issue.level === 'warning' && issue.step ? { accept: `Or, if the user decides to keep it, add accept: {${issue.code}: "<the user's reason>"} to step ${issue.step}.` } : {}) };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The issues that validate prints: every error; every open warning on a changed step (and those not on a step);
|
|
166
|
+
* notes only when asked. Accepted ones are counted.
|
|
167
|
+
*/
|
|
168
|
+
export function shownIssues(report, flags = {}) {
|
|
169
|
+
const open = report.issues.filter(issue => issue.accepted === undefined);
|
|
170
|
+
const errors = open.filter(issue => issue.level === 'error');
|
|
171
|
+
const warnings = open.filter(issue => issue.level === 'warning');
|
|
172
|
+
const fresh = flags.all || !report.changed ? warnings : warnings.filter(issue => !issue.step || report.changed.has(issue.step));
|
|
173
|
+
const listed = fresh;
|
|
174
|
+
const notes = open.filter(issue => issue.level === 'note');
|
|
175
|
+
const hidden = warnings.length - listed.length;
|
|
176
|
+
return {
|
|
177
|
+
issues: [...errors, ...listed, ...(flags.notes || flags.all ? notes : [])].map(formatIssue),
|
|
178
|
+
issue_counts: { errors: errors.length, warnings: warnings.length, notes: notes.length, accepted: report.issues.length - open.length },
|
|
179
|
+
...(hidden ? { more_warnings: `${hidden} more warning${hidden === 1 ? '' : 's'} on unchanged steps. method validate FILE --all lists them.` } : {}),
|
|
180
|
+
...(report.pending ? { checks: `${report.pending} check${report.pending === 1 ? '' : 's'} pending` } : {}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export const guideLine = 'Fix errors. Fix each warning, or accept it with the user\'s reason. Never add a check only to remove a warning.';
|
|
184
|
+
/** The publish gate: errors refuse the publish; every open warning and note is listed. */
|
|
185
|
+
export async function publishIssues(client, file) {
|
|
186
|
+
const { issues } = await collectIssues(file, { phase: 'publish', client });
|
|
187
|
+
const errors = issues.filter(issue => issue.level === 'error');
|
|
188
|
+
if (errors.length)
|
|
189
|
+
throw Object.assign(Error(`Nothing was published. Fix ${errors.length === 1 ? 'this error' : 'these errors'}, then publish again: ${errors.map(issue => `${issue.code}${where(issue) ? ` (${where(issue)})` : ''}: ${issue.message} ${issue.fix}`).join(' ')}`), { code: 'method_has_errors', issues: errors.map(formatIssue) });
|
|
190
|
+
const open = issues.filter(issue => issue.accepted === undefined), accepted = issues.length - open.length;
|
|
191
|
+
return { ...(open.length ? { issues: open.map(formatIssue) } : {}), ...(accepted ? { accepted_issues: accepted } : {}) };
|
|
192
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Outbox } from "./outbox.js";
|
|
2
|
+
import { MethodClient } from "./method-client.js";
|
|
3
|
+
import { type RunInspection } from "../../workflow-language/src/inspection.js";
|
|
4
|
+
export declare class MethodSync {
|
|
5
|
+
readonly client: MethodClient;
|
|
6
|
+
readonly directory: string;
|
|
7
|
+
private state;
|
|
8
|
+
private pending;
|
|
9
|
+
private stopped;
|
|
10
|
+
private sending;
|
|
11
|
+
private initial;
|
|
12
|
+
private warned;
|
|
13
|
+
private live;
|
|
14
|
+
private deviceChecked;
|
|
15
|
+
private timer;
|
|
16
|
+
private scheduled;
|
|
17
|
+
readonly outbox: Outbox;
|
|
18
|
+
constructor(client: MethodClient, directory: string, workflowId: string, versionId: string, runId?: string, device?: boolean, outbox?: Outbox);
|
|
19
|
+
get device(): boolean;
|
|
20
|
+
private get item();
|
|
21
|
+
private save;
|
|
22
|
+
private upload;
|
|
23
|
+
private send;
|
|
24
|
+
/**
|
|
25
|
+
* Record the run's start. This sends nothing and never throws: the run never waits for the account. With live, the
|
|
26
|
+
* outbox starts sending (the version first) and the run's records follow it.
|
|
27
|
+
*/
|
|
28
|
+
start(workflow: RunInspection["workflow"], inputs: RunInspection["inputs"], resources: RunInspection["resources"], live?: boolean): Promise<void>;
|
|
29
|
+
snapshot(): void;
|
|
30
|
+
private capture;
|
|
31
|
+
private warn;
|
|
32
|
+
/**
|
|
33
|
+
* Send the final record: the outbox sends the version first. Resolves "saved" or "pending"; a pending run stays in
|
|
34
|
+
* the outbox. Never throws.
|
|
35
|
+
*/
|
|
36
|
+
finish(failure?: unknown): Promise<"saved" | "pending">;
|
|
37
|
+
private finished;
|
|
38
|
+
private finishOnce;
|
|
39
|
+
/** Send the final local record of a run. Never runs business actions. */
|
|
40
|
+
static retry(directory: string, client?: MethodClient): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The organization setting "Keep run content on devices". When it cannot be read, the run's content stays on this
|
|
44
|
+
* computer: the server would refuse the content anyway when the setting is on.
|
|
45
|
+
*/
|
|
46
|
+
export declare function organizationKeepsContent(client: MethodClient): Promise<boolean>;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { transferResults } from "./result-transfer.js";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { Outbox } from "./outbox.js";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { inspectRun } from "./inspect.js";
|
|
8
|
+
import { writePrivateJson } from "./files.js";
|
|
9
|
+
import { MethodClient } from "./method-client.js";
|
|
10
|
+
import { InspectionSchema, deviceOnly, } from "../../workflow-language/src/inspection.js";
|
|
11
|
+
const SyncSchema = z.object({
|
|
12
|
+
schema: z.literal("method-sync/1"),
|
|
13
|
+
server: z.string(),
|
|
14
|
+
id: z.string().uuid(),
|
|
15
|
+
workflow_id: z.string(),
|
|
16
|
+
version_id: z.string(),
|
|
17
|
+
sequence: z.number().int().nonnegative(),
|
|
18
|
+
dashboard_id: z.string().optional(),
|
|
19
|
+
// The run's content stays on this computer, also when the sync is retried later.
|
|
20
|
+
device: z.literal(true).optional(),
|
|
21
|
+
});
|
|
22
|
+
export class MethodSync {
|
|
23
|
+
client;
|
|
24
|
+
directory;
|
|
25
|
+
state;
|
|
26
|
+
pending = Promise.resolve();
|
|
27
|
+
stopped = false;
|
|
28
|
+
sending = false;
|
|
29
|
+
initial;
|
|
30
|
+
warned = false;
|
|
31
|
+
live = false;
|
|
32
|
+
deviceChecked = false;
|
|
33
|
+
timer;
|
|
34
|
+
scheduled;
|
|
35
|
+
outbox;
|
|
36
|
+
constructor(client, directory, workflowId, versionId, runId,
|
|
37
|
+
// run_data: device, or the organization keeps run content on devices. The account receives the run's shape and
|
|
38
|
+
// timing; its content stays on this computer. The organization setting is read when the first record is sent.
|
|
39
|
+
device = false, outbox) {
|
|
40
|
+
this.client = client;
|
|
41
|
+
this.directory = directory;
|
|
42
|
+
this.outbox = outbox ?? new Outbox(client);
|
|
43
|
+
const path = join(directory, "method-sync.json");
|
|
44
|
+
this.state = existsSync(path)
|
|
45
|
+
? SyncSchema.parse(JSON.parse(readFileSync(path, "utf8")))
|
|
46
|
+
: {
|
|
47
|
+
schema: "method-sync/1",
|
|
48
|
+
server: client.server,
|
|
49
|
+
id: runId ?? randomUUID(),
|
|
50
|
+
workflow_id: workflowId,
|
|
51
|
+
version_id: versionId,
|
|
52
|
+
sequence: 0,
|
|
53
|
+
};
|
|
54
|
+
if (this.state.server !== client.server ||
|
|
55
|
+
this.state.workflow_id !== workflowId ||
|
|
56
|
+
this.state.version_id !== versionId)
|
|
57
|
+
throw Error("This run is linked to another Method or server.");
|
|
58
|
+
if (device)
|
|
59
|
+
this.state.device = true;
|
|
60
|
+
this.save();
|
|
61
|
+
}
|
|
62
|
+
get device() {
|
|
63
|
+
return this.state.device === true;
|
|
64
|
+
}
|
|
65
|
+
get item() {
|
|
66
|
+
return { kind: "run", server: this.client.server, run_dir: this.directory, version_id: this.state.version_id, pid: process.pid };
|
|
67
|
+
}
|
|
68
|
+
save() {
|
|
69
|
+
writePrivateJson(join(this.directory, "method-sync.json"), this.state);
|
|
70
|
+
}
|
|
71
|
+
async upload(inspection) {
|
|
72
|
+
// A run record is sent after its version is on the server.
|
|
73
|
+
if (!await this.outbox.whenSaved(this.state.version_id))
|
|
74
|
+
throw Error("The version is not saved yet.");
|
|
75
|
+
if (!this.device && !this.deviceChecked) {
|
|
76
|
+
if (await organizationKeepsContent(this.client)) {
|
|
77
|
+
this.state.device = true;
|
|
78
|
+
this.save();
|
|
79
|
+
}
|
|
80
|
+
this.deviceChecked = true;
|
|
81
|
+
}
|
|
82
|
+
if (this.device)
|
|
83
|
+
inspection = deviceOnly(inspection);
|
|
84
|
+
const payload = {
|
|
85
|
+
workflow_id: this.state.workflow_id,
|
|
86
|
+
version_id: this.state.version_id,
|
|
87
|
+
sequence: ++this.state.sequence,
|
|
88
|
+
inspection,
|
|
89
|
+
};
|
|
90
|
+
this.save();
|
|
91
|
+
if (inspection.files?.length) {
|
|
92
|
+
// Publish completed work even if transferring its result files is interrupted.
|
|
93
|
+
await this.send({ ...payload, inspection: { ...inspection, files: undefined } });
|
|
94
|
+
payload.sequence = ++this.state.sequence;
|
|
95
|
+
this.save();
|
|
96
|
+
payload.inspection = { ...inspection, files: await transferResults(this.client, this.directory, inspection.files) };
|
|
97
|
+
}
|
|
98
|
+
await this.send(payload);
|
|
99
|
+
}
|
|
100
|
+
async send(payload) {
|
|
101
|
+
const result = await retryTransfer(() => this.client.request(`/api/cli/runs/${this.state.id}`, "PUT", payload, true, { stallMs: 60_000 }));
|
|
102
|
+
if (!this.state.dashboard_id)
|
|
103
|
+
process.stderr.write(`Dashboard: ${this.client.server}/runs/${result.id}\n`);
|
|
104
|
+
this.state.dashboard_id = result.id;
|
|
105
|
+
this.save();
|
|
106
|
+
this.warned = false;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Record the run's start. This sends nothing and never throws: the run never waits for the account. With live, the
|
|
110
|
+
* outbox starts sending (the version first) and the run's records follow it.
|
|
111
|
+
*/
|
|
112
|
+
async start(workflow, inputs, resources, live = true) {
|
|
113
|
+
try {
|
|
114
|
+
if (this.live)
|
|
115
|
+
return;
|
|
116
|
+
this.initial = {
|
|
117
|
+
schema: "workflow-inspection/2",
|
|
118
|
+
workflow,
|
|
119
|
+
run_id: this.state.id,
|
|
120
|
+
status: "running",
|
|
121
|
+
inputs,
|
|
122
|
+
resources,
|
|
123
|
+
invocations: {},
|
|
124
|
+
events: existsSync(join(this.directory, "setup.json")) ? JSON.parse(readFileSync(join(this.directory, "setup.json"), "utf8")) : [],
|
|
125
|
+
};
|
|
126
|
+
// A completed checkpoint remains completed when reconnecting or adding result files.
|
|
127
|
+
try {
|
|
128
|
+
const saved = inspectRun(this.directory, { includeFiles: "references" });
|
|
129
|
+
if (saved.status === 'succeeded')
|
|
130
|
+
this.initial = saved;
|
|
131
|
+
}
|
|
132
|
+
catch { /* Preparation has no checkpoint yet. */ }
|
|
133
|
+
writePrivateJson(join(this.directory, 'setup-inspection.json'), this.initial);
|
|
134
|
+
this.outbox.add(this.item);
|
|
135
|
+
if (!live)
|
|
136
|
+
return;
|
|
137
|
+
this.live = true;
|
|
138
|
+
this.outbox.start();
|
|
139
|
+
const initial = this.initial;
|
|
140
|
+
this.sending = true;
|
|
141
|
+
this.pending = this.upload(initial).catch(error => this.warn(error)).finally(() => { this.sending = false; });
|
|
142
|
+
this.timer = setInterval(() => this.capture(), 5000);
|
|
143
|
+
this.timer.unref();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.warn(error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
snapshot() {
|
|
150
|
+
if (this.stopped || this.scheduled || !this.live)
|
|
151
|
+
return;
|
|
152
|
+
// Coalesce tool bursts before reading the journal or encoding attachments.
|
|
153
|
+
this.scheduled = setTimeout(() => {
|
|
154
|
+
this.scheduled = undefined;
|
|
155
|
+
this.capture();
|
|
156
|
+
}, 1000);
|
|
157
|
+
this.scheduled.unref();
|
|
158
|
+
}
|
|
159
|
+
capture() {
|
|
160
|
+
if (this.stopped)
|
|
161
|
+
return;
|
|
162
|
+
if (this.sending) {
|
|
163
|
+
this.snapshot();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Read synchronously on the SDK event loop, before any await. Atomic SDK files cannot change during this read.
|
|
167
|
+
let inspection;
|
|
168
|
+
try {
|
|
169
|
+
inspection = inspectRun(this.directory, { activeSnapshot: true, includeFiles: "references" });
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.sending = true;
|
|
175
|
+
this.pending = this.upload(inspection).catch(error => this.warn(error)).finally(() => {
|
|
176
|
+
this.sending = false;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
warn(error) {
|
|
180
|
+
// Before the version is saved, the run's last line says what happens; nothing else is printed.
|
|
181
|
+
if (!this.warned && String(error).includes("not saved yet")) {
|
|
182
|
+
this.warned = true;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (!this.warned)
|
|
186
|
+
process.stderr.write(`Dashboard sync paused: ${String(error)}\nRun records remain here; any later method command sends them.\n`);
|
|
187
|
+
this.warned = true;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Send the final record: the outbox sends the version first. Resolves "saved" or "pending"; a pending run stays in
|
|
191
|
+
* the outbox. Never throws.
|
|
192
|
+
*/
|
|
193
|
+
async finish(failure) {
|
|
194
|
+
this.finished ??= this.finishOnce(failure);
|
|
195
|
+
return this.finished;
|
|
196
|
+
}
|
|
197
|
+
finished;
|
|
198
|
+
async finishOnce(failure) {
|
|
199
|
+
this.stopped = true;
|
|
200
|
+
clearInterval(this.timer);
|
|
201
|
+
clearTimeout(this.scheduled);
|
|
202
|
+
try {
|
|
203
|
+
if (!this.initial)
|
|
204
|
+
this.outbox.add(this.item);
|
|
205
|
+
await this.pending;
|
|
206
|
+
await this.outbox.drain();
|
|
207
|
+
let inspection;
|
|
208
|
+
try {
|
|
209
|
+
inspection = inspectRun(this.directory, { includeFiles: "references" });
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
if (!this.initial)
|
|
213
|
+
throw error;
|
|
214
|
+
inspection = {
|
|
215
|
+
...this.initial,
|
|
216
|
+
events: existsSync(join(this.directory, "setup.json")) ? JSON.parse(readFileSync(join(this.directory, "setup.json"), "utf8")) : this.initial.events,
|
|
217
|
+
status: "needs_attention",
|
|
218
|
+
error: failure instanceof Error
|
|
219
|
+
? failure.message
|
|
220
|
+
: String(failure ?? error),
|
|
221
|
+
};
|
|
222
|
+
// A run that failed before its first step keeps this record for a later sync.
|
|
223
|
+
writePrivateJson(join(this.directory, "setup-inspection.json"), inspection);
|
|
224
|
+
}
|
|
225
|
+
await this.upload(inspection);
|
|
226
|
+
this.outbox.remove(this.item);
|
|
227
|
+
return "saved";
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
this.warn(error);
|
|
231
|
+
this.outbox.release(this.item);
|
|
232
|
+
return "pending";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** Send the final local record of a run. Never runs business actions. */
|
|
236
|
+
static async retry(directory, client) {
|
|
237
|
+
const state = SyncSchema.parse(JSON.parse(readFileSync(join(directory, "method-sync.json"), "utf8")));
|
|
238
|
+
const sync = new MethodSync(client ?? new MethodClient(state.server), directory, state.workflow_id, state.version_id);
|
|
239
|
+
let inspection;
|
|
240
|
+
try {
|
|
241
|
+
inspection = inspectRun(directory, { includeFiles: "references" });
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
if (!existsSync(join(directory, "setup-inspection.json")))
|
|
245
|
+
throw error;
|
|
246
|
+
inspection = InspectionSchema.parse(JSON.parse(readFileSync(join(directory, "setup-inspection.json"), "utf8")));
|
|
247
|
+
}
|
|
248
|
+
if (state.dashboard_id) {
|
|
249
|
+
const saved = await sync.client.request(`/api/workspace/runs/${state.dashboard_id}`);
|
|
250
|
+
// A later observation can change a finished run's status; then the local record replaces the saved one.
|
|
251
|
+
if (saved.run.status === "succeeded" && inspection.status === "succeeded") {
|
|
252
|
+
// Keep bytes already saved if the local file was removed or changed.
|
|
253
|
+
const files = [...(saved.run.inspection.files ?? [])];
|
|
254
|
+
for (const file of inspection.files ?? []) {
|
|
255
|
+
const index = files.findIndex(old => old.path === file.path && old.sha256 === file.sha256);
|
|
256
|
+
if (index >= 0 && files[index].stored)
|
|
257
|
+
continue;
|
|
258
|
+
if (index >= 0)
|
|
259
|
+
files[index] = file;
|
|
260
|
+
else
|
|
261
|
+
files.push(file);
|
|
262
|
+
}
|
|
263
|
+
inspection = { ...saved.run.inspection, files };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
await sync.upload(inspection);
|
|
267
|
+
sync.outbox.remove(sync.item);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async function retryTransfer(send) {
|
|
271
|
+
for (let attempt = 0;; attempt++)
|
|
272
|
+
try {
|
|
273
|
+
return await send();
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (attempt >= 2 || /\b4\d\d:/.test(String(error)) && !String(error).includes('429:'))
|
|
277
|
+
throw error;
|
|
278
|
+
await new Promise(r => setTimeout(r, 500 * 2 ** attempt));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The organization setting "Keep run content on devices". When it cannot be read, the run's content stays on this
|
|
283
|
+
* computer: the server would refuse the content anyway when the setting is on.
|
|
284
|
+
*/
|
|
285
|
+
export async function organizationKeepsContent(client) {
|
|
286
|
+
try {
|
|
287
|
+
const me = await client.request("/api/cli/me");
|
|
288
|
+
return me?.organization?.keep_run_content_on_devices === true;
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { MethodClient } from "./method-client.js";
|
|
3
|
+
/** The next step for a failure before the first step. */
|
|
4
|
+
export declare function setupFix(error: any): any;
|
|
5
|
+
export declare function methodMain(args?: string[], clientFactory?: (server: string) => MethodClient): Promise<boolean | void>;
|