@productlobster/plob 0.1.2 → 0.1.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/README.md +20 -1
- package/dist/cli.js +280 -45
- package/dist/index.d.ts +1 -0
- package/dist/index.js +280 -45
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -76,12 +76,31 @@ Use an explicit idempotency key for a scripted retry:
|
|
|
76
76
|
plob observe --kind signal --content "Three users asked for CSV export" --idempotency-key "$JOB_ID"
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
Fetch and extract a prototype pack:
|
|
79
|
+
Fetch and extract a prototype pack into a new directory:
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
82
|
plob prototype-pack --prototype-id proto_123 --out ./proto
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
The output directory must not already exist, and its parent must exist. Choose a
|
|
86
|
+
new name for each extraction; existing files, empty directories, and symlink
|
|
87
|
+
output roots are rejected without being changed. The CLI does not merge or
|
|
88
|
+
overwrite an earlier extraction. Parent path aliases are resolved to their
|
|
89
|
+
canonical location, which is returned as `extraction.outDir`. Create missing
|
|
90
|
+
parent directories yourself before running the command.
|
|
91
|
+
|
|
92
|
+
Extraction creates a fresh tree, uses exclusive file creation, and removes that
|
|
93
|
+
new tree if extraction fails. Unsafe archive paths and colliding filenames are
|
|
94
|
+
rejected, including case or Unicode aliases recognized by the host filesystem.
|
|
95
|
+
Directory-entry paths are validated, but empty directories are not extracted;
|
|
96
|
+
parent directories are created as needed for files. Inconsistent ZIP directory
|
|
97
|
+
counts, payload ranges, and decoded sizes are rejected. On POSIX the new
|
|
98
|
+
directories and files use permissions 0700 and 0600; Windows inherits the
|
|
99
|
+
caller's filesystem permissions. Use a trusted, stable parent directory on a
|
|
100
|
+
local filesystem: this does not defend against another process replacing parent
|
|
101
|
+
directories during extraction, and exclusive creation may not be reliable on
|
|
102
|
+
every network filesystem.
|
|
103
|
+
|
|
85
104
|
`plob upload` attaches one file to the selected workspace. Use the workspace `id` from `plob workspaces`, not the display name. The connection must include the `write:upload` scope. Supported extensions: `.pdf`, `.docx`, `.txt`, `.md`, `.markdown`, `.png`, `.jpg`, `.jpeg`, and `.webp`; files must be non-empty and at most 10 MB.
|
|
86
105
|
|
|
87
106
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -26,9 +26,9 @@ ProductLobster runs product analyses and keeps a product workspace's documents a
|
|
|
26
26
|
A full analysis is asynchronous and durable. The loop is the same for every workflow:
|
|
27
27
|
|
|
28
28
|
1. **Start.** Call \`run-start\` with a \`workflow\` of \`launch\`, \`audit\`, \`focused-analysis\`, \`prototype\`, or \`refinement\`, plus its inputs. It returns a run id. If your request is too thin, it returns a \`needs_sharpening\` result that lists what to add \u2014 add detail and call \`run-start\` again.
|
|
29
|
-
2. **Poll.** Call \`run-status\` with the run id. It reports the current status and returns a \`since\` cursor; pass that cursor on the next poll to get only what changed. Runs take minutes \u2014 poll, do not block.
|
|
29
|
+
2. **Poll.** Call \`run-status\` with the run id. It reports the current status and returns a \`since\` cursor; pass that cursor on the next poll to get only what changed. Runs take minutes \u2014 poll, do not block. Stop polling when the run's \`status\` is \`completed\`, \`failed\`, or \`cancelled\`. Do not wait for every stage to leave \`pending\`: a run that fails or is cancelled part-way leaves its unstarted stages \`pending\` permanently, and on a finished run \`pending\` means the stage never ran.
|
|
30
30
|
3. **Resolve checkpoints.** When \`run-status\` reports that the run is waiting at a checkpoint, it lists the options. Pick one and call \`checkpoint-resolve\` with the run id and an \`optionId\` taken from that list. An invented or stale id is rejected.
|
|
31
|
-
4. **Read the result.** When the run is complete, \`run-output\` returns the structured result. Use \`document-list\` and \`document-get\` to read the documents the run produced, and \`prototype-list\` / \`prototype-pack\` for any prototype.
|
|
31
|
+
4. **Read the result.** When the run is complete, \`run-output\` returns the structured result as generated. Its \`stageEdits\` list marks each stage whose document the user has changed since (\`hasUserEdits: true\`). When such an entry also carries a \`documentId\`, read that document with \`document-get\` rather than relying on the structured result. An entry with \`hasUserEdits: true\` and no \`documentId\` means the stage was edited but its document is not available through \`document-get\`; use \`output\` for that stage. Use \`document-list\` and \`document-get\` to read the documents the run produced, and \`prototype-list\` / \`prototype-pack\` for any prototype.
|
|
32
32
|
|
|
33
33
|
Other run controls: \`run-cancel\` stops a run; \`run-replay\` re-runs one, optionally from a stage.
|
|
34
34
|
|
|
@@ -61,6 +61,7 @@ Every failure is a structured object: \`{ code, message, requestId, retryHint }\
|
|
|
61
61
|
| \`invalid_arguments\` | A field was the wrong shape | Fix the named fields and retry |
|
|
62
62
|
| \`conflict\` | State changed under you (e.g. a stale document token) | Re-read the current state, then retry |
|
|
63
63
|
| \`rate_limited\` | Daily call budget or a short spike | Wait for the reset the result reports |
|
|
64
|
+
| \`allotment_exhausted\` | Included outcomes have been used; the key is read-only | Continue with read tools or ask the person to upgrade |
|
|
64
65
|
|
|
65
66
|
Always keep \`requestId\` \u2014 it is the one join key for support.
|
|
66
67
|
|
|
@@ -14601,6 +14602,8 @@ var RUN_START_REFINEMENT_INSTRUCTION_MAX_LENGTH = 2e3;
|
|
|
14601
14602
|
var runStartWorkflowSchema = external_exports.enum(["audit", "launch", "focused-analysis", "prototype", "refinement"]).describe("Which Brain action to start.");
|
|
14602
14603
|
var runStartInputSchema = external_exports.object({
|
|
14603
14604
|
workflow: runStartWorkflowSchema,
|
|
14605
|
+
/** `audit`: deliberate acknowledgement when server-owned readiness has gaps. */
|
|
14606
|
+
proceedWithGaps: external_exports.boolean().optional().describe("Proceed with server-recorded audit evidence gaps."),
|
|
14604
14607
|
/** `launch`: the product idea to take through the launch pipeline. */
|
|
14605
14608
|
idea: external_exports.string().optional().describe("Product idea for the launch workflow."),
|
|
14606
14609
|
/** `focused-analysis`: the question to analyze against workspace context. */
|
|
@@ -14685,6 +14688,7 @@ var DOCUMENT_TYPE_VALUES = [
|
|
|
14685
14688
|
"customer-research",
|
|
14686
14689
|
"competitive-analysis",
|
|
14687
14690
|
"customer-demand-analysis",
|
|
14691
|
+
"kill-shot-refutation",
|
|
14688
14692
|
"strategic-framing",
|
|
14689
14693
|
"solution-design",
|
|
14690
14694
|
"prd-generation",
|
|
@@ -14705,7 +14709,7 @@ var documentContentInputSchema = documentContentSchema.refine(
|
|
|
14705
14709
|
"Document content must not be empty"
|
|
14706
14710
|
).describe("Document content to write.");
|
|
14707
14711
|
var documentListInputSchema = external_exports.object({
|
|
14708
|
-
cursor: external_exports.string().optional().describe("Opaque cursor from a previous page."),
|
|
14712
|
+
cursor: external_exports.string().max(4096).optional().describe("Opaque cursor from a previous page."),
|
|
14709
14713
|
latest: external_exports.boolean().optional().describe("Return only the newest matching document."),
|
|
14710
14714
|
limit: external_exports.number().int().min(1).max(100).optional().describe("Maximum number of documents to return; server default is 20."),
|
|
14711
14715
|
runId: external_exports.string().optional().describe("Identifier of the run to include."),
|
|
@@ -14753,7 +14757,7 @@ var documentPutOutputSchema = external_exports.object({
|
|
|
14753
14757
|
|
|
14754
14758
|
// ../agentic-contract/prototype-list.ts
|
|
14755
14759
|
var prototypeListInputSchema = external_exports.object({
|
|
14756
|
-
cursor: external_exports.string().optional().describe("Opaque cursor from a previous response.")
|
|
14760
|
+
cursor: external_exports.string().max(4096).optional().describe("Opaque cursor from a previous response.")
|
|
14757
14761
|
});
|
|
14758
14762
|
var prototypeStatusEnum = external_exports.enum(["generating", "complete", "failed"]).describe("Coarse prototype done-ness status.");
|
|
14759
14763
|
var prototypeSummarySchema = external_exports.object({
|
|
@@ -14949,7 +14953,10 @@ var ERROR_CODES = [
|
|
|
14949
14953
|
"version_revoked",
|
|
14950
14954
|
// Appended (PRO-1484) — append-only so older clients never see a leading
|
|
14951
14955
|
// member shift; no AGENTIC_CONTRACT_SCHEMA_VERSION bump (additive code).
|
|
14952
|
-
"service_unavailable"
|
|
14956
|
+
"service_unavailable",
|
|
14957
|
+
// Appended (PRO-1920) — distinct from a periodic rate limit so clients can
|
|
14958
|
+
// render the durable read-only conversion state without parsing prose.
|
|
14959
|
+
"allotment_exhausted"
|
|
14953
14960
|
];
|
|
14954
14961
|
var errorCodeSchema = external_exports.enum(ERROR_CODES);
|
|
14955
14962
|
var agenticErrorSchema = external_exports.object({
|
|
@@ -15088,25 +15095,90 @@ var runReplayOutputSchema = external_exports.object({
|
|
|
15088
15095
|
});
|
|
15089
15096
|
|
|
15090
15097
|
// ../agentic-contract/run-output.ts
|
|
15098
|
+
var RUN_OUTPUT_MAX_OUTPUT_BYTES = 65536;
|
|
15099
|
+
var RUN_OUTPUT_MAX_RESPONSE_BYTES = 98304;
|
|
15100
|
+
var RUN_OUTPUT_ENVELOPE_RESERVE_BYTES = 1024;
|
|
15101
|
+
var RUN_OUTPUT_MAX_DOCUMENT_IDS = 200;
|
|
15102
|
+
var RUN_OUTPUT_MAX_OMITTED_KEYS = 64;
|
|
15103
|
+
var RUN_OUTPUT_MAX_OMITTED_KEY_BYTES = 128;
|
|
15104
|
+
var RUN_OUTPUT_MAX_STAGE_EDITS = 32;
|
|
15105
|
+
var utf8Encoder2 = new TextEncoder();
|
|
15106
|
+
function measureRunOutputBytes(value) {
|
|
15107
|
+
const json2 = JSON.stringify(value);
|
|
15108
|
+
return json2 === void 0 ? 0 : utf8Encoder2.encode(json2).byteLength;
|
|
15109
|
+
}
|
|
15091
15110
|
var runOutputInputSchema = external_exports.object({
|
|
15092
15111
|
runId: external_exports.string().describe("Identifier of the run to read output from."),
|
|
15093
|
-
stage: external_exports.string().optional().describe("Stage name to scope output to; omit for run-level output.")
|
|
15112
|
+
stage: external_exports.string().trim().min(1).optional().describe("Stage name to scope output to; omit for run-level output.")
|
|
15094
15113
|
});
|
|
15095
|
-
var
|
|
15114
|
+
var runOutputStageEditFields = {
|
|
15115
|
+
stage: external_exports.string().describe("Stage name, as passed to run-output's stage."),
|
|
15116
|
+
hasUserEdits: external_exports.boolean().describe(
|
|
15117
|
+
"True when the user changed this stage's document after it was generated, so output is out of date for this stage."
|
|
15118
|
+
),
|
|
15119
|
+
documentId: external_exports.string().optional().describe(
|
|
15120
|
+
"The changed document, to read with document-get. Set only when hasUserEdits is true and document-get can read it; absent on an edited stage, use output."
|
|
15121
|
+
)
|
|
15122
|
+
};
|
|
15123
|
+
var runOutputFieldsSchema = external_exports.object({
|
|
15096
15124
|
runId: external_exports.string().describe("Identifier of the run."),
|
|
15097
15125
|
stage: external_exports.string().nullable().describe("Stage this output is scoped to, or null for run-level."),
|
|
15098
|
-
output: external_exports.record(external_exports.string(), external_exports.unknown()).
|
|
15099
|
-
|
|
15126
|
+
output: external_exports.record(external_exports.string(), external_exports.unknown()).refine(
|
|
15127
|
+
(value) => measureRunOutputBytes(value) <= RUN_OUTPUT_MAX_OUTPUT_BYTES,
|
|
15128
|
+
"Run output must not exceed 64 KiB"
|
|
15129
|
+
).describe(
|
|
15130
|
+
"Structured output, at most 64 KiB of JSON: summaries and IDs, never large inline bodies."
|
|
15131
|
+
),
|
|
15132
|
+
documentIds: external_exports.array(external_exports.string()).max(RUN_OUTPUT_MAX_DOCUMENT_IDS).describe("IDs of referenced documents to fetch via document-get."),
|
|
15133
|
+
truncated: external_exports.boolean().optional().describe(
|
|
15134
|
+
"True when entries were left out of output to stay within the size limit."
|
|
15135
|
+
),
|
|
15136
|
+
omittedKeys: external_exports.array(
|
|
15137
|
+
external_exports.string().refine(
|
|
15138
|
+
(key) => measureRunOutputBytes(key) <= RUN_OUTPUT_MAX_OMITTED_KEY_BYTES,
|
|
15139
|
+
"Omitted key must not exceed 128 bytes"
|
|
15140
|
+
)
|
|
15141
|
+
).max(RUN_OUTPUT_MAX_OMITTED_KEYS).optional().describe(
|
|
15142
|
+
"Top-level output keys left out for size, in order and at most 64: stage names for run-level output (request each by stage), field names for one stage (read the document via document-get)."
|
|
15143
|
+
),
|
|
15144
|
+
omittedKeyCount: external_exports.number().int().nonnegative().optional().describe("How many top-level output keys were left out for size."),
|
|
15145
|
+
omittedKeysTruncated: external_exports.boolean().optional().describe(
|
|
15146
|
+
"True when omittedKeys lists fewer keys than omittedKeyCount (too many, or a key too long to list)."
|
|
15147
|
+
),
|
|
15148
|
+
prototypeId: external_exports.string().optional().describe(
|
|
15149
|
+
"Set when prototype content was left out for size: the run's prototype, to fetch with prototype-pack."
|
|
15150
|
+
),
|
|
15151
|
+
stageEdits: external_exports.array(
|
|
15152
|
+
external_exports.object({
|
|
15153
|
+
...runOutputStageEditFields,
|
|
15154
|
+
stage: runOutputStageEditFields.stage.refine(
|
|
15155
|
+
(stage) => measureRunOutputBytes(stage) <= RUN_OUTPUT_MAX_OMITTED_KEY_BYTES,
|
|
15156
|
+
"Stage name must not exceed 128 bytes"
|
|
15157
|
+
)
|
|
15158
|
+
})
|
|
15159
|
+
).max(RUN_OUTPUT_MAX_STAGE_EDITS).optional().describe(
|
|
15160
|
+
"One entry per stage with output, at most 32: whether the user changed that stage's document, and if so the document to read with document-get."
|
|
15100
15161
|
),
|
|
15101
|
-
documentIds: external_exports.array(external_exports.string()).describe("IDs of referenced documents to fetch via document-get."),
|
|
15102
15162
|
schemaVersion: schemaVersionSchema
|
|
15103
15163
|
});
|
|
15164
|
+
var runOutputOutputSchema = runOutputFieldsSchema.refine(
|
|
15165
|
+
(response) => measureRunOutputBytes(response) <= RUN_OUTPUT_MAX_RESPONSE_BYTES - RUN_OUTPUT_ENVELOPE_RESERVE_BYTES,
|
|
15166
|
+
"Run output response must not exceed 96 KiB with its envelope"
|
|
15167
|
+
);
|
|
15168
|
+
var runOutputParseSchema = runOutputFieldsSchema.extend({
|
|
15169
|
+
output: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
15170
|
+
documentIds: external_exports.array(external_exports.string()),
|
|
15171
|
+
omittedKeys: external_exports.array(external_exports.string()).optional(),
|
|
15172
|
+
stageEdits: external_exports.array(external_exports.object(runOutputStageEditFields)).optional()
|
|
15173
|
+
});
|
|
15104
15174
|
|
|
15105
15175
|
// ../agentic-contract/run-status.ts
|
|
15106
15176
|
var runStageProgressSchema = external_exports.object({
|
|
15107
15177
|
name: external_exports.string().describe("Stage identifier."),
|
|
15108
15178
|
order: external_exports.number().int().positive().describe("1-based position in the run."),
|
|
15109
|
-
status: external_exports.enum(["pending", "running", "completed", "failed"]).describe(
|
|
15179
|
+
status: external_exports.enum(["pending", "running", "completed", "failed"]).describe(
|
|
15180
|
+
"Coarse per-stage status. On a run whose status is completed, failed, or cancelled, pending means the stage never ran; stop polling on the run status, not on stage statuses."
|
|
15181
|
+
)
|
|
15110
15182
|
});
|
|
15111
15183
|
var runTransitionSchema = external_exports.object({
|
|
15112
15184
|
from: runStatusEnum.nullable().describe("Prior status, or null for the first transition."),
|
|
@@ -15180,6 +15252,9 @@ var AGENTIC_TOOL_NAMES = [
|
|
|
15180
15252
|
];
|
|
15181
15253
|
|
|
15182
15254
|
// ../agentic-contract/workspace-identity.ts
|
|
15255
|
+
var workspaceListInputSchema = external_exports.object({
|
|
15256
|
+
cursor: external_exports.string().optional().describe("Opaque cursor from a previous response (reserved for Phase 2).")
|
|
15257
|
+
});
|
|
15183
15258
|
var workspacePipelineTypeSchema = external_exports.enum(["LAUNCH", "AUDIT"]);
|
|
15184
15259
|
var workspaceIdentitySchema = external_exports.object({
|
|
15185
15260
|
id: external_exports.string().describe("Stable workspace identifier."),
|
|
@@ -15294,6 +15369,7 @@ var EXIT_CODES = {
|
|
|
15294
15369
|
rateLimited: 16,
|
|
15295
15370
|
versionRevoked: 17,
|
|
15296
15371
|
serviceUnavailable: 18,
|
|
15372
|
+
allotmentExhausted: 19,
|
|
15297
15373
|
network: 70
|
|
15298
15374
|
};
|
|
15299
15375
|
var ERROR_CODE_EXIT_CODES = {
|
|
@@ -15308,7 +15384,8 @@ var ERROR_CODE_EXIT_CODES = {
|
|
|
15308
15384
|
version_revoked: EXIT_CODES.versionRevoked,
|
|
15309
15385
|
// PRO-1484: transient backend outage — its own non-zero code so scripts can
|
|
15310
15386
|
// distinguish a retryable outage from a hard internal fault (exit 1).
|
|
15311
|
-
service_unavailable: EXIT_CODES.serviceUnavailable
|
|
15387
|
+
service_unavailable: EXIT_CODES.serviceUnavailable,
|
|
15388
|
+
allotment_exhausted: EXIT_CODES.allotmentExhausted
|
|
15312
15389
|
};
|
|
15313
15390
|
function mapErrorCodeToExitCode(code) {
|
|
15314
15391
|
return ERROR_CODE_EXIT_CODES[code];
|
|
@@ -15864,6 +15941,17 @@ function parseCommaSeparatedList(value) {
|
|
|
15864
15941
|
const items = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
15865
15942
|
return items.length > 0 ? items : void 0;
|
|
15866
15943
|
}
|
|
15944
|
+
function isUnsafeTerminalCodePoint(codePoint) {
|
|
15945
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 || codePoint === 1564 || codePoint === 8206 || codePoint === 8207 || codePoint === 8232 || codePoint === 8233 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
|
|
15946
|
+
}
|
|
15947
|
+
function escapeTerminalText(value, maxCodePoints) {
|
|
15948
|
+
const codePoints = Array.from(value);
|
|
15949
|
+
const escaped = codePoints.slice(0, maxCodePoints).map((character) => {
|
|
15950
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
15951
|
+
return isUnsafeTerminalCodePoint(codePoint) ? `\\u${codePoint.toString(16).padStart(4, "0")}` : character;
|
|
15952
|
+
}).join("");
|
|
15953
|
+
return codePoints.length > maxCodePoints ? `${escaped}\u2026` : escaped;
|
|
15954
|
+
}
|
|
15867
15955
|
function isRecord2(value) {
|
|
15868
15956
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15869
15957
|
}
|
|
@@ -16811,44 +16899,86 @@ function renderPrototypeList(response) {
|
|
|
16811
16899
|
}
|
|
16812
16900
|
|
|
16813
16901
|
// src/runtime/zip-extract.ts
|
|
16814
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
16902
|
+
import { mkdir, realpath, rm, writeFile } from "fs/promises";
|
|
16815
16903
|
import path2 from "path";
|
|
16816
16904
|
import { inflateRawSync } from "zlib";
|
|
16817
16905
|
var END_OF_CENTRAL_DIRECTORY_SIGNATURE = 101010256;
|
|
16818
16906
|
var CENTRAL_DIRECTORY_FILE_SIGNATURE = 33639248;
|
|
16907
|
+
var CENTRAL_DIRECTORY_DIGITAL_SIGNATURE = 84233040;
|
|
16819
16908
|
var LOCAL_FILE_HEADER_SIGNATURE = 67324752;
|
|
16820
16909
|
var ZIP64_SENTINEL = 65535;
|
|
16821
16910
|
var ZIP64_SIZE_SENTINEL = 4294967295;
|
|
16911
|
+
var TRAILING_WINDOWS_ALIAS = /[ .]$/;
|
|
16912
|
+
var RESERVED_WINDOWS_DEVICE = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i;
|
|
16822
16913
|
var MAX_ENTRY_DECOMPRESSED_BYTES = 64 * 1024 * 1024;
|
|
16823
16914
|
var MAX_TOTAL_DECOMPRESSED_BYTES = 256 * 1024 * 1024;
|
|
16824
16915
|
var MAX_ENTRY_COUNT = 4096;
|
|
16916
|
+
var ZipOutputDirectoryError = class extends Error {
|
|
16917
|
+
constructor(message) {
|
|
16918
|
+
super(message);
|
|
16919
|
+
this.name = "ZipOutputDirectoryError";
|
|
16920
|
+
}
|
|
16921
|
+
};
|
|
16825
16922
|
async function extractZipToDirectory(bytes, outDir) {
|
|
16826
16923
|
const archive = Buffer.from(bytes);
|
|
16827
|
-
const
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16924
|
+
const directory = readCentralDirectory(archive);
|
|
16925
|
+
const root = await createOutputRoot(outDir);
|
|
16926
|
+
try {
|
|
16927
|
+
return await extractEntries(archive, directory, root);
|
|
16928
|
+
} catch (error51) {
|
|
16929
|
+
await rm(root, { recursive: true, force: true });
|
|
16930
|
+
throw error51;
|
|
16931
|
+
}
|
|
16932
|
+
}
|
|
16933
|
+
async function createOutputRoot(outDir) {
|
|
16934
|
+
const requestedRoot = path2.resolve(outDir);
|
|
16935
|
+
let parent;
|
|
16936
|
+
try {
|
|
16937
|
+
parent = await realpath(path2.dirname(requestedRoot));
|
|
16938
|
+
} catch (error51) {
|
|
16939
|
+
if (hasFilesystemCode(error51, "ENOENT") || hasFilesystemCode(error51, "ENOTDIR")) {
|
|
16940
|
+
throw new ZipOutputDirectoryError(
|
|
16941
|
+
"The output parent directory must already exist. Create the parent, then choose a new --out directory."
|
|
16942
|
+
);
|
|
16943
|
+
}
|
|
16944
|
+
throw error51;
|
|
16945
|
+
}
|
|
16946
|
+
const root = path2.join(parent, path2.basename(requestedRoot));
|
|
16947
|
+
try {
|
|
16948
|
+
await mkdir(root, { mode: 448 });
|
|
16949
|
+
} catch (error51) {
|
|
16950
|
+
if (hasFilesystemCode(error51, "EEXIST")) {
|
|
16951
|
+
throw new ZipOutputDirectoryError(
|
|
16952
|
+
"The output directory must not already exist. Choose a new --out directory; existing files were not changed."
|
|
16953
|
+
);
|
|
16954
|
+
}
|
|
16955
|
+
if (hasFilesystemCode(error51, "ENOTDIR")) {
|
|
16956
|
+
throw new ZipOutputDirectoryError(
|
|
16957
|
+
"The output parent directory must already exist. Create the parent, then choose a new --out directory."
|
|
16958
|
+
);
|
|
16959
|
+
}
|
|
16960
|
+
throw error51;
|
|
16834
16961
|
}
|
|
16962
|
+
return root;
|
|
16963
|
+
}
|
|
16964
|
+
async function extractEntries(archive, directory, root) {
|
|
16835
16965
|
const files = [];
|
|
16836
16966
|
let totalDecompressedBytes = 0;
|
|
16837
|
-
for (const entry of entries) {
|
|
16967
|
+
for (const entry of directory.entries) {
|
|
16838
16968
|
const normalizedName = normalizeZipEntryName(entry.name);
|
|
16839
|
-
|
|
16969
|
+
const targetPath = resolveSafeTarget(root, normalizedName);
|
|
16970
|
+
if (normalizedName.endsWith("/")) {
|
|
16840
16971
|
continue;
|
|
16841
16972
|
}
|
|
16842
|
-
const
|
|
16843
|
-
const contents = readEntryContents(archive, entry);
|
|
16973
|
+
const contents = readEntryContents(archive, entry, directory.offset);
|
|
16844
16974
|
totalDecompressedBytes += contents.length;
|
|
16845
16975
|
if (totalDecompressedBytes > MAX_TOTAL_DECOMPRESSED_BYTES) {
|
|
16846
16976
|
throw new Error(
|
|
16847
16977
|
`ZIP archive exceeds the maximum decompressed size of ${MAX_TOTAL_DECOMPRESSED_BYTES} bytes.`
|
|
16848
16978
|
);
|
|
16849
16979
|
}
|
|
16850
|
-
await mkdir(path2.dirname(targetPath), { recursive: true });
|
|
16851
|
-
await writeFile(targetPath, contents);
|
|
16980
|
+
await mkdir(path2.dirname(targetPath), { recursive: true, mode: 448 });
|
|
16981
|
+
await writeFile(targetPath, contents, { flag: "wx", mode: 384 });
|
|
16852
16982
|
files.push(normalizedName);
|
|
16853
16983
|
}
|
|
16854
16984
|
return {
|
|
@@ -16857,39 +16987,60 @@ async function extractZipToDirectory(bytes, outDir) {
|
|
|
16857
16987
|
outDir: root
|
|
16858
16988
|
};
|
|
16859
16989
|
}
|
|
16990
|
+
function hasFilesystemCode(error51, code) {
|
|
16991
|
+
return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === code;
|
|
16992
|
+
}
|
|
16860
16993
|
function readCentralDirectory(archive) {
|
|
16861
16994
|
const eocdOffset = findEndOfCentralDirectory(archive);
|
|
16862
16995
|
const entryCount = archive.readUInt16LE(eocdOffset + 10);
|
|
16863
16996
|
const centralDirectorySize = archive.readUInt32LE(eocdOffset + 12);
|
|
16864
|
-
|
|
16865
|
-
if (entryCount === ZIP64_SENTINEL || centralDirectorySize === ZIP64_SIZE_SENTINEL ||
|
|
16997
|
+
const directoryOffset = archive.readUInt32LE(eocdOffset + 16);
|
|
16998
|
+
if (entryCount === ZIP64_SENTINEL || centralDirectorySize === ZIP64_SIZE_SENTINEL || directoryOffset === ZIP64_SIZE_SENTINEL) {
|
|
16866
16999
|
throw new Error("ZIP64 prototype packs are not supported.");
|
|
16867
17000
|
}
|
|
16868
|
-
|
|
17001
|
+
if (entryCount > MAX_ENTRY_COUNT) {
|
|
17002
|
+
throw new Error(
|
|
17003
|
+
`ZIP archive exceeds the maximum of ${MAX_ENTRY_COUNT} entries.`
|
|
17004
|
+
);
|
|
17005
|
+
}
|
|
17006
|
+
const endOffset = directoryOffset + centralDirectorySize;
|
|
17007
|
+
if (endOffset > eocdOffset) {
|
|
17008
|
+
throw new Error("Invalid ZIP central directory.");
|
|
17009
|
+
}
|
|
16869
17010
|
const entries = [];
|
|
16870
|
-
|
|
16871
|
-
|
|
17011
|
+
let offset = directoryOffset;
|
|
17012
|
+
while (entries.length < entryCount) {
|
|
17013
|
+
if (offset + 46 > endOffset || archive.readUInt32LE(offset) !== CENTRAL_DIRECTORY_FILE_SIGNATURE) {
|
|
16872
17014
|
throw new Error("Invalid ZIP central directory.");
|
|
16873
17015
|
}
|
|
16874
17016
|
const flags = archive.readUInt16LE(offset + 8);
|
|
16875
17017
|
const compressionMethod = archive.readUInt16LE(offset + 10);
|
|
16876
17018
|
const compressedSize = archive.readUInt32LE(offset + 20);
|
|
17019
|
+
const uncompressedSize = archive.readUInt32LE(offset + 24);
|
|
16877
17020
|
const localHeaderOffset = archive.readUInt32LE(offset + 42);
|
|
16878
17021
|
const nameLength = archive.readUInt16LE(offset + 28);
|
|
16879
17022
|
const extraLength = archive.readUInt16LE(offset + 30);
|
|
16880
17023
|
const commentLength = archive.readUInt16LE(offset + 32);
|
|
16881
17024
|
const nameStart = offset + 46;
|
|
17025
|
+
const nextOffset = nameStart + nameLength + extraLength + commentLength;
|
|
17026
|
+
if (nextOffset > endOffset) {
|
|
17027
|
+
throw new Error("Invalid ZIP central directory.");
|
|
17028
|
+
}
|
|
16882
17029
|
const name = archive.subarray(nameStart, nameStart + nameLength).toString("utf8");
|
|
16883
17030
|
entries.push({
|
|
16884
17031
|
compressedSize,
|
|
16885
17032
|
compressionMethod,
|
|
16886
17033
|
flags,
|
|
16887
17034
|
localHeaderOffset,
|
|
16888
|
-
name
|
|
17035
|
+
name,
|
|
17036
|
+
uncompressedSize
|
|
16889
17037
|
});
|
|
16890
|
-
offset =
|
|
17038
|
+
offset = nextOffset;
|
|
17039
|
+
}
|
|
17040
|
+
if (offset !== endOffset && (offset + 6 > endOffset || archive.readUInt32LE(offset) !== CENTRAL_DIRECTORY_DIGITAL_SIGNATURE || offset + 6 + archive.readUInt16LE(offset + 4) !== endOffset)) {
|
|
17041
|
+
throw new Error("Invalid ZIP central directory.");
|
|
16891
17042
|
}
|
|
16892
|
-
return entries;
|
|
17043
|
+
return { entries, offset: directoryOffset };
|
|
16893
17044
|
}
|
|
16894
17045
|
function findEndOfCentralDirectory(archive) {
|
|
16895
17046
|
const minOffset = Math.max(0, archive.length - 65557);
|
|
@@ -16900,16 +17051,19 @@ function findEndOfCentralDirectory(archive) {
|
|
|
16900
17051
|
}
|
|
16901
17052
|
throw new Error("Invalid ZIP archive.");
|
|
16902
17053
|
}
|
|
16903
|
-
function readEntryContents(archive, entry) {
|
|
17054
|
+
function readEntryContents(archive, entry, directoryOffset) {
|
|
16904
17055
|
if (entry.flags % 2 === 1) {
|
|
16905
17056
|
throw new Error("Encrypted ZIP entries are not supported.");
|
|
16906
17057
|
}
|
|
16907
|
-
if (archive.readUInt32LE(entry.localHeaderOffset) !== LOCAL_FILE_HEADER_SIGNATURE) {
|
|
17058
|
+
if (entry.localHeaderOffset + 30 > directoryOffset || archive.readUInt32LE(entry.localHeaderOffset) !== LOCAL_FILE_HEADER_SIGNATURE) {
|
|
16908
17059
|
throw new Error("Invalid ZIP local file header.");
|
|
16909
17060
|
}
|
|
16910
17061
|
const localNameLength = archive.readUInt16LE(entry.localHeaderOffset + 26);
|
|
16911
17062
|
const localExtraLength = archive.readUInt16LE(entry.localHeaderOffset + 28);
|
|
16912
17063
|
const dataStart = entry.localHeaderOffset + 30 + localNameLength + localExtraLength;
|
|
17064
|
+
if (dataStart + entry.compressedSize > directoryOffset) {
|
|
17065
|
+
throw new Error("Invalid ZIP entry data range.");
|
|
17066
|
+
}
|
|
16913
17067
|
const compressed = archive.subarray(
|
|
16914
17068
|
dataStart,
|
|
16915
17069
|
dataStart + entry.compressedSize
|
|
@@ -16920,13 +17074,20 @@ function readEntryContents(archive, entry) {
|
|
|
16920
17074
|
`ZIP entry exceeds the maximum decompressed size of ${MAX_ENTRY_DECOMPRESSED_BYTES} bytes.`
|
|
16921
17075
|
);
|
|
16922
17076
|
}
|
|
17077
|
+
if (compressed.length !== entry.uncompressedSize) {
|
|
17078
|
+
throw new Error("Invalid ZIP entry decompressed size.");
|
|
17079
|
+
}
|
|
16923
17080
|
return Buffer.from(compressed);
|
|
16924
17081
|
}
|
|
16925
17082
|
if (entry.compressionMethod === 8) {
|
|
16926
17083
|
try {
|
|
16927
|
-
|
|
17084
|
+
const contents = inflateRawSync(compressed, {
|
|
16928
17085
|
maxOutputLength: MAX_ENTRY_DECOMPRESSED_BYTES
|
|
16929
17086
|
});
|
|
17087
|
+
if (contents.length !== entry.uncompressedSize) {
|
|
17088
|
+
throw new Error("Invalid ZIP entry decompressed size.");
|
|
17089
|
+
}
|
|
17090
|
+
return contents;
|
|
16930
17091
|
} catch (error51) {
|
|
16931
17092
|
if (isMaxOutputLengthError(error51)) {
|
|
16932
17093
|
throw new Error(
|
|
@@ -16945,15 +17106,21 @@ function isMaxOutputLengthError(error51) {
|
|
|
16945
17106
|
}
|
|
16946
17107
|
function normalizeZipEntryName(name) {
|
|
16947
17108
|
const normalized = name.replaceAll("\\", "/");
|
|
16948
|
-
if (normalized.length === 0 || normalized.startsWith("/") || normalized.includes("\0")) {
|
|
16949
|
-
|
|
17109
|
+
if (normalized.length === 0 || normalized.startsWith("/") || normalized.includes(":") || normalized.includes("\0") || normalized.split("/").some(isUnsafeWindowsComponent)) {
|
|
17110
|
+
throw new Error(`Unsafe ZIP entry path: ${name}`);
|
|
16950
17111
|
}
|
|
16951
17112
|
return normalized;
|
|
16952
17113
|
}
|
|
17114
|
+
function isUnsafeWindowsComponent(component) {
|
|
17115
|
+
if (component === "." || component === "..") {
|
|
17116
|
+
return false;
|
|
17117
|
+
}
|
|
17118
|
+
return TRAILING_WINDOWS_ALIAS.test(component) || RESERVED_WINDOWS_DEVICE.test(component);
|
|
17119
|
+
}
|
|
16953
17120
|
function resolveSafeTarget(root, entryName) {
|
|
16954
17121
|
const targetPath = path2.resolve(root, entryName);
|
|
16955
17122
|
const relative = path2.relative(root, targetPath);
|
|
16956
|
-
if (relative.startsWith(
|
|
17123
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
16957
17124
|
throw new Error(`Unsafe ZIP entry path: ${entryName}`);
|
|
16958
17125
|
}
|
|
16959
17126
|
return targetPath;
|
|
@@ -16961,7 +17128,7 @@ function resolveSafeTarget(root, entryName) {
|
|
|
16961
17128
|
|
|
16962
17129
|
// src/commands/prototype-pack.ts
|
|
16963
17130
|
function registerPrototypePackCommand(program, options) {
|
|
16964
|
-
const command = program.command("prototype-pack").description("Fetch a prototype ZIP reference.").requiredOption("--prototype-id <id>", "Prototype id.").option("--out <dir>", "
|
|
17131
|
+
const command = program.command("prototype-pack").description("Fetch a prototype ZIP reference.").requiredOption("--prototype-id <id>", "Prototype id.").option("--out <dir>", "New output directory (parent must already exist).");
|
|
16965
17132
|
command.action(async (commandOptions) => {
|
|
16966
17133
|
const exitCode2 = await runPrototypePackCommand({
|
|
16967
17134
|
commandOptions,
|
|
@@ -17061,7 +17228,20 @@ async function fetchAndExtractPack(input) {
|
|
|
17061
17228
|
});
|
|
17062
17229
|
}
|
|
17063
17230
|
const archive = new Uint8Array(await response.arrayBuffer());
|
|
17064
|
-
|
|
17231
|
+
try {
|
|
17232
|
+
return await extractZipToDirectory(archive, input.outDir);
|
|
17233
|
+
} catch (error51) {
|
|
17234
|
+
if (error51 instanceof ZipOutputDirectoryError) {
|
|
17235
|
+
throw createCliOperationalError({
|
|
17236
|
+
code: "internal",
|
|
17237
|
+
message: error51.message
|
|
17238
|
+
});
|
|
17239
|
+
}
|
|
17240
|
+
throw createCliOperationalError({
|
|
17241
|
+
code: "internal",
|
|
17242
|
+
message: "The prototype ZIP could not be safely extracted. Check the output parent permissions and retry with a new --out directory; if the failure persists, report it."
|
|
17243
|
+
});
|
|
17244
|
+
}
|
|
17065
17245
|
}
|
|
17066
17246
|
function isSameOrigin(packUrl, endpoint) {
|
|
17067
17247
|
try {
|
|
@@ -17231,7 +17411,9 @@ function runRunOutputCommand(options) {
|
|
|
17231
17411
|
}
|
|
17232
17412
|
return runCommand(
|
|
17233
17413
|
{
|
|
17234
|
-
|
|
17414
|
+
// The tolerant consumer schema: a newer plob still reads a server that
|
|
17415
|
+
// predates the run-output size bounds.
|
|
17416
|
+
outputSchema: runOutputParseSchema,
|
|
17235
17417
|
renderHuman: renderRunOutput,
|
|
17236
17418
|
toolArguments: compactRecord({
|
|
17237
17419
|
runId: runId.value,
|
|
@@ -17246,16 +17428,69 @@ function runRunOutputCommand(options) {
|
|
|
17246
17428
|
}
|
|
17247
17429
|
);
|
|
17248
17430
|
}
|
|
17431
|
+
var OMITTED_KEY_DISPLAY_MAX = 80;
|
|
17432
|
+
var PROTOTYPE_ID_DISPLAY_MAX = 80;
|
|
17249
17433
|
function renderRunOutput(response) {
|
|
17250
17434
|
const scope = response.stage ? ` stage ${response.stage}` : "";
|
|
17251
17435
|
const lines = [`Run ${response.runId}${scope} output`];
|
|
17252
17436
|
lines.push(`Fields: ${Object.keys(response.output).length}`);
|
|
17437
|
+
const omitted = renderOmittedKeys(response);
|
|
17438
|
+
if (omitted) {
|
|
17439
|
+
lines.push(`Omitted for size: ${omitted}`);
|
|
17440
|
+
}
|
|
17441
|
+
if (response.prototypeId !== void 0) {
|
|
17442
|
+
const prototypeId = escapeTerminalText(
|
|
17443
|
+
response.prototypeId,
|
|
17444
|
+
PROTOTYPE_ID_DISPLAY_MAX
|
|
17445
|
+
);
|
|
17446
|
+
lines.push(
|
|
17447
|
+
`Prototype: ${prototypeId} (fetch with \`plob prototype-pack --prototype-id ${prototypeId}\`)`
|
|
17448
|
+
);
|
|
17449
|
+
}
|
|
17450
|
+
const edited = renderEditedStages(response);
|
|
17451
|
+
if (edited) {
|
|
17452
|
+
lines.push(
|
|
17453
|
+
`Edited by the user (read with \`plob document-get\`): ${edited}`
|
|
17454
|
+
);
|
|
17455
|
+
}
|
|
17253
17456
|
if (response.documentIds.length > 0) {
|
|
17254
|
-
lines.push(
|
|
17457
|
+
lines.push(
|
|
17458
|
+
`Documents: ${response.documentIds.map((id) => escapeTerminalText(id, DOCUMENT_ID_DISPLAY_MAX)).join(", ")}`
|
|
17459
|
+
);
|
|
17255
17460
|
}
|
|
17256
17461
|
return `${lines.join("\n")}
|
|
17257
17462
|
`;
|
|
17258
17463
|
}
|
|
17464
|
+
var DOCUMENT_ID_DISPLAY_MAX = 80;
|
|
17465
|
+
function renderEditedStages(response) {
|
|
17466
|
+
const edited = (response.stageEdits ?? []).filter(
|
|
17467
|
+
(entry) => entry.hasUserEdits
|
|
17468
|
+
);
|
|
17469
|
+
if (edited.length === 0) {
|
|
17470
|
+
return;
|
|
17471
|
+
}
|
|
17472
|
+
return edited.map((entry) => {
|
|
17473
|
+
const stage = escapeTerminalText(entry.stage, OMITTED_KEY_DISPLAY_MAX);
|
|
17474
|
+
return entry.documentId === void 0 ? stage : `${stage} (${escapeTerminalText(entry.documentId, DOCUMENT_ID_DISPLAY_MAX)})`;
|
|
17475
|
+
}).join(", ");
|
|
17476
|
+
}
|
|
17477
|
+
function renderOmittedKeys(response) {
|
|
17478
|
+
if (response.truncated !== true) {
|
|
17479
|
+
return;
|
|
17480
|
+
}
|
|
17481
|
+
const listed = (response.omittedKeys ?? []).map(
|
|
17482
|
+
(key) => escapeTerminalText(key, OMITTED_KEY_DISPLAY_MAX)
|
|
17483
|
+
);
|
|
17484
|
+
const unlisted = Math.max(
|
|
17485
|
+
0,
|
|
17486
|
+
(response.omittedKeyCount ?? listed.length) - listed.length
|
|
17487
|
+
);
|
|
17488
|
+
if (listed.length === 0) {
|
|
17489
|
+
return unlisted > 0 ? `${unlisted} ${unlisted === 1 ? "entry" : "entries"}` : void 0;
|
|
17490
|
+
}
|
|
17491
|
+
const names = listed.join(", ");
|
|
17492
|
+
return unlisted > 0 ? `${names} (and ${unlisted} more)` : names;
|
|
17493
|
+
}
|
|
17259
17494
|
|
|
17260
17495
|
// src/commands/run-replay.ts
|
|
17261
17496
|
function registerRunReplayCommand(program, options) {
|
|
@@ -17368,7 +17603,7 @@ function renderRunStatus(response) {
|
|
|
17368
17603
|
// src/commands/upload.ts
|
|
17369
17604
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
17370
17605
|
import path3 from "path";
|
|
17371
|
-
var MAX_UPLOAD_FILE_SIZE_BYTES =
|
|
17606
|
+
var MAX_UPLOAD_FILE_SIZE_BYTES = 25 * 1024 * 1024;
|
|
17372
17607
|
var DOCUMENT_UPLOAD_FIELD_NAME = "files";
|
|
17373
17608
|
var MIME_TYPES_BY_EXTENSION = /* @__PURE__ */ new Map([
|
|
17374
17609
|
[
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -25,9 +25,9 @@ ProductLobster runs product analyses and keeps a product workspace's documents a
|
|
|
25
25
|
A full analysis is asynchronous and durable. The loop is the same for every workflow:
|
|
26
26
|
|
|
27
27
|
1. **Start.** Call \`run-start\` with a \`workflow\` of \`launch\`, \`audit\`, \`focused-analysis\`, \`prototype\`, or \`refinement\`, plus its inputs. It returns a run id. If your request is too thin, it returns a \`needs_sharpening\` result that lists what to add \u2014 add detail and call \`run-start\` again.
|
|
28
|
-
2. **Poll.** Call \`run-status\` with the run id. It reports the current status and returns a \`since\` cursor; pass that cursor on the next poll to get only what changed. Runs take minutes \u2014 poll, do not block.
|
|
28
|
+
2. **Poll.** Call \`run-status\` with the run id. It reports the current status and returns a \`since\` cursor; pass that cursor on the next poll to get only what changed. Runs take minutes \u2014 poll, do not block. Stop polling when the run's \`status\` is \`completed\`, \`failed\`, or \`cancelled\`. Do not wait for every stage to leave \`pending\`: a run that fails or is cancelled part-way leaves its unstarted stages \`pending\` permanently, and on a finished run \`pending\` means the stage never ran.
|
|
29
29
|
3. **Resolve checkpoints.** When \`run-status\` reports that the run is waiting at a checkpoint, it lists the options. Pick one and call \`checkpoint-resolve\` with the run id and an \`optionId\` taken from that list. An invented or stale id is rejected.
|
|
30
|
-
4. **Read the result.** When the run is complete, \`run-output\` returns the structured result. Use \`document-list\` and \`document-get\` to read the documents the run produced, and \`prototype-list\` / \`prototype-pack\` for any prototype.
|
|
30
|
+
4. **Read the result.** When the run is complete, \`run-output\` returns the structured result as generated. Its \`stageEdits\` list marks each stage whose document the user has changed since (\`hasUserEdits: true\`). When such an entry also carries a \`documentId\`, read that document with \`document-get\` rather than relying on the structured result. An entry with \`hasUserEdits: true\` and no \`documentId\` means the stage was edited but its document is not available through \`document-get\`; use \`output\` for that stage. Use \`document-list\` and \`document-get\` to read the documents the run produced, and \`prototype-list\` / \`prototype-pack\` for any prototype.
|
|
31
31
|
|
|
32
32
|
Other run controls: \`run-cancel\` stops a run; \`run-replay\` re-runs one, optionally from a stage.
|
|
33
33
|
|
|
@@ -60,6 +60,7 @@ Every failure is a structured object: \`{ code, message, requestId, retryHint }\
|
|
|
60
60
|
| \`invalid_arguments\` | A field was the wrong shape | Fix the named fields and retry |
|
|
61
61
|
| \`conflict\` | State changed under you (e.g. a stale document token) | Re-read the current state, then retry |
|
|
62
62
|
| \`rate_limited\` | Daily call budget or a short spike | Wait for the reset the result reports |
|
|
63
|
+
| \`allotment_exhausted\` | Included outcomes have been used; the key is read-only | Continue with read tools or ask the person to upgrade |
|
|
63
64
|
|
|
64
65
|
Always keep \`requestId\` \u2014 it is the one join key for support.
|
|
65
66
|
|
|
@@ -14600,6 +14601,8 @@ var RUN_START_REFINEMENT_INSTRUCTION_MAX_LENGTH = 2e3;
|
|
|
14600
14601
|
var runStartWorkflowSchema = external_exports.enum(["audit", "launch", "focused-analysis", "prototype", "refinement"]).describe("Which Brain action to start.");
|
|
14601
14602
|
var runStartInputSchema = external_exports.object({
|
|
14602
14603
|
workflow: runStartWorkflowSchema,
|
|
14604
|
+
/** `audit`: deliberate acknowledgement when server-owned readiness has gaps. */
|
|
14605
|
+
proceedWithGaps: external_exports.boolean().optional().describe("Proceed with server-recorded audit evidence gaps."),
|
|
14603
14606
|
/** `launch`: the product idea to take through the launch pipeline. */
|
|
14604
14607
|
idea: external_exports.string().optional().describe("Product idea for the launch workflow."),
|
|
14605
14608
|
/** `focused-analysis`: the question to analyze against workspace context. */
|
|
@@ -14684,6 +14687,7 @@ var DOCUMENT_TYPE_VALUES = [
|
|
|
14684
14687
|
"customer-research",
|
|
14685
14688
|
"competitive-analysis",
|
|
14686
14689
|
"customer-demand-analysis",
|
|
14690
|
+
"kill-shot-refutation",
|
|
14687
14691
|
"strategic-framing",
|
|
14688
14692
|
"solution-design",
|
|
14689
14693
|
"prd-generation",
|
|
@@ -14704,7 +14708,7 @@ var documentContentInputSchema = documentContentSchema.refine(
|
|
|
14704
14708
|
"Document content must not be empty"
|
|
14705
14709
|
).describe("Document content to write.");
|
|
14706
14710
|
var documentListInputSchema = external_exports.object({
|
|
14707
|
-
cursor: external_exports.string().optional().describe("Opaque cursor from a previous page."),
|
|
14711
|
+
cursor: external_exports.string().max(4096).optional().describe("Opaque cursor from a previous page."),
|
|
14708
14712
|
latest: external_exports.boolean().optional().describe("Return only the newest matching document."),
|
|
14709
14713
|
limit: external_exports.number().int().min(1).max(100).optional().describe("Maximum number of documents to return; server default is 20."),
|
|
14710
14714
|
runId: external_exports.string().optional().describe("Identifier of the run to include."),
|
|
@@ -14752,7 +14756,7 @@ var documentPutOutputSchema = external_exports.object({
|
|
|
14752
14756
|
|
|
14753
14757
|
// ../agentic-contract/prototype-list.ts
|
|
14754
14758
|
var prototypeListInputSchema = external_exports.object({
|
|
14755
|
-
cursor: external_exports.string().optional().describe("Opaque cursor from a previous response.")
|
|
14759
|
+
cursor: external_exports.string().max(4096).optional().describe("Opaque cursor from a previous response.")
|
|
14756
14760
|
});
|
|
14757
14761
|
var prototypeStatusEnum = external_exports.enum(["generating", "complete", "failed"]).describe("Coarse prototype done-ness status.");
|
|
14758
14762
|
var prototypeSummarySchema = external_exports.object({
|
|
@@ -14948,7 +14952,10 @@ var ERROR_CODES = [
|
|
|
14948
14952
|
"version_revoked",
|
|
14949
14953
|
// Appended (PRO-1484) — append-only so older clients never see a leading
|
|
14950
14954
|
// member shift; no AGENTIC_CONTRACT_SCHEMA_VERSION bump (additive code).
|
|
14951
|
-
"service_unavailable"
|
|
14955
|
+
"service_unavailable",
|
|
14956
|
+
// Appended (PRO-1920) — distinct from a periodic rate limit so clients can
|
|
14957
|
+
// render the durable read-only conversion state without parsing prose.
|
|
14958
|
+
"allotment_exhausted"
|
|
14952
14959
|
];
|
|
14953
14960
|
var errorCodeSchema = external_exports.enum(ERROR_CODES);
|
|
14954
14961
|
var agenticErrorSchema = external_exports.object({
|
|
@@ -15087,25 +15094,90 @@ var runReplayOutputSchema = external_exports.object({
|
|
|
15087
15094
|
});
|
|
15088
15095
|
|
|
15089
15096
|
// ../agentic-contract/run-output.ts
|
|
15097
|
+
var RUN_OUTPUT_MAX_OUTPUT_BYTES = 65536;
|
|
15098
|
+
var RUN_OUTPUT_MAX_RESPONSE_BYTES = 98304;
|
|
15099
|
+
var RUN_OUTPUT_ENVELOPE_RESERVE_BYTES = 1024;
|
|
15100
|
+
var RUN_OUTPUT_MAX_DOCUMENT_IDS = 200;
|
|
15101
|
+
var RUN_OUTPUT_MAX_OMITTED_KEYS = 64;
|
|
15102
|
+
var RUN_OUTPUT_MAX_OMITTED_KEY_BYTES = 128;
|
|
15103
|
+
var RUN_OUTPUT_MAX_STAGE_EDITS = 32;
|
|
15104
|
+
var utf8Encoder2 = new TextEncoder();
|
|
15105
|
+
function measureRunOutputBytes(value) {
|
|
15106
|
+
const json2 = JSON.stringify(value);
|
|
15107
|
+
return json2 === void 0 ? 0 : utf8Encoder2.encode(json2).byteLength;
|
|
15108
|
+
}
|
|
15090
15109
|
var runOutputInputSchema = external_exports.object({
|
|
15091
15110
|
runId: external_exports.string().describe("Identifier of the run to read output from."),
|
|
15092
|
-
stage: external_exports.string().optional().describe("Stage name to scope output to; omit for run-level output.")
|
|
15111
|
+
stage: external_exports.string().trim().min(1).optional().describe("Stage name to scope output to; omit for run-level output.")
|
|
15093
15112
|
});
|
|
15094
|
-
var
|
|
15113
|
+
var runOutputStageEditFields = {
|
|
15114
|
+
stage: external_exports.string().describe("Stage name, as passed to run-output's stage."),
|
|
15115
|
+
hasUserEdits: external_exports.boolean().describe(
|
|
15116
|
+
"True when the user changed this stage's document after it was generated, so output is out of date for this stage."
|
|
15117
|
+
),
|
|
15118
|
+
documentId: external_exports.string().optional().describe(
|
|
15119
|
+
"The changed document, to read with document-get. Set only when hasUserEdits is true and document-get can read it; absent on an edited stage, use output."
|
|
15120
|
+
)
|
|
15121
|
+
};
|
|
15122
|
+
var runOutputFieldsSchema = external_exports.object({
|
|
15095
15123
|
runId: external_exports.string().describe("Identifier of the run."),
|
|
15096
15124
|
stage: external_exports.string().nullable().describe("Stage this output is scoped to, or null for run-level."),
|
|
15097
|
-
output: external_exports.record(external_exports.string(), external_exports.unknown()).
|
|
15098
|
-
|
|
15125
|
+
output: external_exports.record(external_exports.string(), external_exports.unknown()).refine(
|
|
15126
|
+
(value) => measureRunOutputBytes(value) <= RUN_OUTPUT_MAX_OUTPUT_BYTES,
|
|
15127
|
+
"Run output must not exceed 64 KiB"
|
|
15128
|
+
).describe(
|
|
15129
|
+
"Structured output, at most 64 KiB of JSON: summaries and IDs, never large inline bodies."
|
|
15130
|
+
),
|
|
15131
|
+
documentIds: external_exports.array(external_exports.string()).max(RUN_OUTPUT_MAX_DOCUMENT_IDS).describe("IDs of referenced documents to fetch via document-get."),
|
|
15132
|
+
truncated: external_exports.boolean().optional().describe(
|
|
15133
|
+
"True when entries were left out of output to stay within the size limit."
|
|
15134
|
+
),
|
|
15135
|
+
omittedKeys: external_exports.array(
|
|
15136
|
+
external_exports.string().refine(
|
|
15137
|
+
(key) => measureRunOutputBytes(key) <= RUN_OUTPUT_MAX_OMITTED_KEY_BYTES,
|
|
15138
|
+
"Omitted key must not exceed 128 bytes"
|
|
15139
|
+
)
|
|
15140
|
+
).max(RUN_OUTPUT_MAX_OMITTED_KEYS).optional().describe(
|
|
15141
|
+
"Top-level output keys left out for size, in order and at most 64: stage names for run-level output (request each by stage), field names for one stage (read the document via document-get)."
|
|
15142
|
+
),
|
|
15143
|
+
omittedKeyCount: external_exports.number().int().nonnegative().optional().describe("How many top-level output keys were left out for size."),
|
|
15144
|
+
omittedKeysTruncated: external_exports.boolean().optional().describe(
|
|
15145
|
+
"True when omittedKeys lists fewer keys than omittedKeyCount (too many, or a key too long to list)."
|
|
15146
|
+
),
|
|
15147
|
+
prototypeId: external_exports.string().optional().describe(
|
|
15148
|
+
"Set when prototype content was left out for size: the run's prototype, to fetch with prototype-pack."
|
|
15149
|
+
),
|
|
15150
|
+
stageEdits: external_exports.array(
|
|
15151
|
+
external_exports.object({
|
|
15152
|
+
...runOutputStageEditFields,
|
|
15153
|
+
stage: runOutputStageEditFields.stage.refine(
|
|
15154
|
+
(stage) => measureRunOutputBytes(stage) <= RUN_OUTPUT_MAX_OMITTED_KEY_BYTES,
|
|
15155
|
+
"Stage name must not exceed 128 bytes"
|
|
15156
|
+
)
|
|
15157
|
+
})
|
|
15158
|
+
).max(RUN_OUTPUT_MAX_STAGE_EDITS).optional().describe(
|
|
15159
|
+
"One entry per stage with output, at most 32: whether the user changed that stage's document, and if so the document to read with document-get."
|
|
15099
15160
|
),
|
|
15100
|
-
documentIds: external_exports.array(external_exports.string()).describe("IDs of referenced documents to fetch via document-get."),
|
|
15101
15161
|
schemaVersion: schemaVersionSchema
|
|
15102
15162
|
});
|
|
15163
|
+
var runOutputOutputSchema = runOutputFieldsSchema.refine(
|
|
15164
|
+
(response) => measureRunOutputBytes(response) <= RUN_OUTPUT_MAX_RESPONSE_BYTES - RUN_OUTPUT_ENVELOPE_RESERVE_BYTES,
|
|
15165
|
+
"Run output response must not exceed 96 KiB with its envelope"
|
|
15166
|
+
);
|
|
15167
|
+
var runOutputParseSchema = runOutputFieldsSchema.extend({
|
|
15168
|
+
output: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
15169
|
+
documentIds: external_exports.array(external_exports.string()),
|
|
15170
|
+
omittedKeys: external_exports.array(external_exports.string()).optional(),
|
|
15171
|
+
stageEdits: external_exports.array(external_exports.object(runOutputStageEditFields)).optional()
|
|
15172
|
+
});
|
|
15103
15173
|
|
|
15104
15174
|
// ../agentic-contract/run-status.ts
|
|
15105
15175
|
var runStageProgressSchema = external_exports.object({
|
|
15106
15176
|
name: external_exports.string().describe("Stage identifier."),
|
|
15107
15177
|
order: external_exports.number().int().positive().describe("1-based position in the run."),
|
|
15108
|
-
status: external_exports.enum(["pending", "running", "completed", "failed"]).describe(
|
|
15178
|
+
status: external_exports.enum(["pending", "running", "completed", "failed"]).describe(
|
|
15179
|
+
"Coarse per-stage status. On a run whose status is completed, failed, or cancelled, pending means the stage never ran; stop polling on the run status, not on stage statuses."
|
|
15180
|
+
)
|
|
15109
15181
|
});
|
|
15110
15182
|
var runTransitionSchema = external_exports.object({
|
|
15111
15183
|
from: runStatusEnum.nullable().describe("Prior status, or null for the first transition."),
|
|
@@ -15179,6 +15251,9 @@ var AGENTIC_TOOL_NAMES = [
|
|
|
15179
15251
|
];
|
|
15180
15252
|
|
|
15181
15253
|
// ../agentic-contract/workspace-identity.ts
|
|
15254
|
+
var workspaceListInputSchema = external_exports.object({
|
|
15255
|
+
cursor: external_exports.string().optional().describe("Opaque cursor from a previous response (reserved for Phase 2).")
|
|
15256
|
+
});
|
|
15182
15257
|
var workspacePipelineTypeSchema = external_exports.enum(["LAUNCH", "AUDIT"]);
|
|
15183
15258
|
var workspaceIdentitySchema = external_exports.object({
|
|
15184
15259
|
id: external_exports.string().describe("Stable workspace identifier."),
|
|
@@ -15293,6 +15368,7 @@ var EXIT_CODES = {
|
|
|
15293
15368
|
rateLimited: 16,
|
|
15294
15369
|
versionRevoked: 17,
|
|
15295
15370
|
serviceUnavailable: 18,
|
|
15371
|
+
allotmentExhausted: 19,
|
|
15296
15372
|
network: 70
|
|
15297
15373
|
};
|
|
15298
15374
|
var ERROR_CODE_EXIT_CODES = {
|
|
@@ -15307,7 +15383,8 @@ var ERROR_CODE_EXIT_CODES = {
|
|
|
15307
15383
|
version_revoked: EXIT_CODES.versionRevoked,
|
|
15308
15384
|
// PRO-1484: transient backend outage — its own non-zero code so scripts can
|
|
15309
15385
|
// distinguish a retryable outage from a hard internal fault (exit 1).
|
|
15310
|
-
service_unavailable: EXIT_CODES.serviceUnavailable
|
|
15386
|
+
service_unavailable: EXIT_CODES.serviceUnavailable,
|
|
15387
|
+
allotment_exhausted: EXIT_CODES.allotmentExhausted
|
|
15311
15388
|
};
|
|
15312
15389
|
function mapErrorCodeToExitCode(code) {
|
|
15313
15390
|
return ERROR_CODE_EXIT_CODES[code];
|
|
@@ -15863,6 +15940,17 @@ function parseCommaSeparatedList(value) {
|
|
|
15863
15940
|
const items = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
15864
15941
|
return items.length > 0 ? items : void 0;
|
|
15865
15942
|
}
|
|
15943
|
+
function isUnsafeTerminalCodePoint(codePoint) {
|
|
15944
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 || codePoint === 1564 || codePoint === 8206 || codePoint === 8207 || codePoint === 8232 || codePoint === 8233 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
|
|
15945
|
+
}
|
|
15946
|
+
function escapeTerminalText(value, maxCodePoints) {
|
|
15947
|
+
const codePoints = Array.from(value);
|
|
15948
|
+
const escaped = codePoints.slice(0, maxCodePoints).map((character) => {
|
|
15949
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
15950
|
+
return isUnsafeTerminalCodePoint(codePoint) ? `\\u${codePoint.toString(16).padStart(4, "0")}` : character;
|
|
15951
|
+
}).join("");
|
|
15952
|
+
return codePoints.length > maxCodePoints ? `${escaped}\u2026` : escaped;
|
|
15953
|
+
}
|
|
15866
15954
|
function isRecord2(value) {
|
|
15867
15955
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15868
15956
|
}
|
|
@@ -16810,44 +16898,86 @@ function renderPrototypeList(response) {
|
|
|
16810
16898
|
}
|
|
16811
16899
|
|
|
16812
16900
|
// src/runtime/zip-extract.ts
|
|
16813
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
16901
|
+
import { mkdir, realpath, rm, writeFile } from "fs/promises";
|
|
16814
16902
|
import path2 from "path";
|
|
16815
16903
|
import { inflateRawSync } from "zlib";
|
|
16816
16904
|
var END_OF_CENTRAL_DIRECTORY_SIGNATURE = 101010256;
|
|
16817
16905
|
var CENTRAL_DIRECTORY_FILE_SIGNATURE = 33639248;
|
|
16906
|
+
var CENTRAL_DIRECTORY_DIGITAL_SIGNATURE = 84233040;
|
|
16818
16907
|
var LOCAL_FILE_HEADER_SIGNATURE = 67324752;
|
|
16819
16908
|
var ZIP64_SENTINEL = 65535;
|
|
16820
16909
|
var ZIP64_SIZE_SENTINEL = 4294967295;
|
|
16910
|
+
var TRAILING_WINDOWS_ALIAS = /[ .]$/;
|
|
16911
|
+
var RESERVED_WINDOWS_DEVICE = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i;
|
|
16821
16912
|
var MAX_ENTRY_DECOMPRESSED_BYTES = 64 * 1024 * 1024;
|
|
16822
16913
|
var MAX_TOTAL_DECOMPRESSED_BYTES = 256 * 1024 * 1024;
|
|
16823
16914
|
var MAX_ENTRY_COUNT = 4096;
|
|
16915
|
+
var ZipOutputDirectoryError = class extends Error {
|
|
16916
|
+
constructor(message) {
|
|
16917
|
+
super(message);
|
|
16918
|
+
this.name = "ZipOutputDirectoryError";
|
|
16919
|
+
}
|
|
16920
|
+
};
|
|
16824
16921
|
async function extractZipToDirectory(bytes, outDir) {
|
|
16825
16922
|
const archive = Buffer.from(bytes);
|
|
16826
|
-
const
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16923
|
+
const directory = readCentralDirectory(archive);
|
|
16924
|
+
const root = await createOutputRoot(outDir);
|
|
16925
|
+
try {
|
|
16926
|
+
return await extractEntries(archive, directory, root);
|
|
16927
|
+
} catch (error51) {
|
|
16928
|
+
await rm(root, { recursive: true, force: true });
|
|
16929
|
+
throw error51;
|
|
16930
|
+
}
|
|
16931
|
+
}
|
|
16932
|
+
async function createOutputRoot(outDir) {
|
|
16933
|
+
const requestedRoot = path2.resolve(outDir);
|
|
16934
|
+
let parent;
|
|
16935
|
+
try {
|
|
16936
|
+
parent = await realpath(path2.dirname(requestedRoot));
|
|
16937
|
+
} catch (error51) {
|
|
16938
|
+
if (hasFilesystemCode(error51, "ENOENT") || hasFilesystemCode(error51, "ENOTDIR")) {
|
|
16939
|
+
throw new ZipOutputDirectoryError(
|
|
16940
|
+
"The output parent directory must already exist. Create the parent, then choose a new --out directory."
|
|
16941
|
+
);
|
|
16942
|
+
}
|
|
16943
|
+
throw error51;
|
|
16944
|
+
}
|
|
16945
|
+
const root = path2.join(parent, path2.basename(requestedRoot));
|
|
16946
|
+
try {
|
|
16947
|
+
await mkdir(root, { mode: 448 });
|
|
16948
|
+
} catch (error51) {
|
|
16949
|
+
if (hasFilesystemCode(error51, "EEXIST")) {
|
|
16950
|
+
throw new ZipOutputDirectoryError(
|
|
16951
|
+
"The output directory must not already exist. Choose a new --out directory; existing files were not changed."
|
|
16952
|
+
);
|
|
16953
|
+
}
|
|
16954
|
+
if (hasFilesystemCode(error51, "ENOTDIR")) {
|
|
16955
|
+
throw new ZipOutputDirectoryError(
|
|
16956
|
+
"The output parent directory must already exist. Create the parent, then choose a new --out directory."
|
|
16957
|
+
);
|
|
16958
|
+
}
|
|
16959
|
+
throw error51;
|
|
16833
16960
|
}
|
|
16961
|
+
return root;
|
|
16962
|
+
}
|
|
16963
|
+
async function extractEntries(archive, directory, root) {
|
|
16834
16964
|
const files = [];
|
|
16835
16965
|
let totalDecompressedBytes = 0;
|
|
16836
|
-
for (const entry of entries) {
|
|
16966
|
+
for (const entry of directory.entries) {
|
|
16837
16967
|
const normalizedName = normalizeZipEntryName(entry.name);
|
|
16838
|
-
|
|
16968
|
+
const targetPath = resolveSafeTarget(root, normalizedName);
|
|
16969
|
+
if (normalizedName.endsWith("/")) {
|
|
16839
16970
|
continue;
|
|
16840
16971
|
}
|
|
16841
|
-
const
|
|
16842
|
-
const contents = readEntryContents(archive, entry);
|
|
16972
|
+
const contents = readEntryContents(archive, entry, directory.offset);
|
|
16843
16973
|
totalDecompressedBytes += contents.length;
|
|
16844
16974
|
if (totalDecompressedBytes > MAX_TOTAL_DECOMPRESSED_BYTES) {
|
|
16845
16975
|
throw new Error(
|
|
16846
16976
|
`ZIP archive exceeds the maximum decompressed size of ${MAX_TOTAL_DECOMPRESSED_BYTES} bytes.`
|
|
16847
16977
|
);
|
|
16848
16978
|
}
|
|
16849
|
-
await mkdir(path2.dirname(targetPath), { recursive: true });
|
|
16850
|
-
await writeFile(targetPath, contents);
|
|
16979
|
+
await mkdir(path2.dirname(targetPath), { recursive: true, mode: 448 });
|
|
16980
|
+
await writeFile(targetPath, contents, { flag: "wx", mode: 384 });
|
|
16851
16981
|
files.push(normalizedName);
|
|
16852
16982
|
}
|
|
16853
16983
|
return {
|
|
@@ -16856,39 +16986,60 @@ async function extractZipToDirectory(bytes, outDir) {
|
|
|
16856
16986
|
outDir: root
|
|
16857
16987
|
};
|
|
16858
16988
|
}
|
|
16989
|
+
function hasFilesystemCode(error51, code) {
|
|
16990
|
+
return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === code;
|
|
16991
|
+
}
|
|
16859
16992
|
function readCentralDirectory(archive) {
|
|
16860
16993
|
const eocdOffset = findEndOfCentralDirectory(archive);
|
|
16861
16994
|
const entryCount = archive.readUInt16LE(eocdOffset + 10);
|
|
16862
16995
|
const centralDirectorySize = archive.readUInt32LE(eocdOffset + 12);
|
|
16863
|
-
|
|
16864
|
-
if (entryCount === ZIP64_SENTINEL || centralDirectorySize === ZIP64_SIZE_SENTINEL ||
|
|
16996
|
+
const directoryOffset = archive.readUInt32LE(eocdOffset + 16);
|
|
16997
|
+
if (entryCount === ZIP64_SENTINEL || centralDirectorySize === ZIP64_SIZE_SENTINEL || directoryOffset === ZIP64_SIZE_SENTINEL) {
|
|
16865
16998
|
throw new Error("ZIP64 prototype packs are not supported.");
|
|
16866
16999
|
}
|
|
16867
|
-
|
|
17000
|
+
if (entryCount > MAX_ENTRY_COUNT) {
|
|
17001
|
+
throw new Error(
|
|
17002
|
+
`ZIP archive exceeds the maximum of ${MAX_ENTRY_COUNT} entries.`
|
|
17003
|
+
);
|
|
17004
|
+
}
|
|
17005
|
+
const endOffset = directoryOffset + centralDirectorySize;
|
|
17006
|
+
if (endOffset > eocdOffset) {
|
|
17007
|
+
throw new Error("Invalid ZIP central directory.");
|
|
17008
|
+
}
|
|
16868
17009
|
const entries = [];
|
|
16869
|
-
|
|
16870
|
-
|
|
17010
|
+
let offset = directoryOffset;
|
|
17011
|
+
while (entries.length < entryCount) {
|
|
17012
|
+
if (offset + 46 > endOffset || archive.readUInt32LE(offset) !== CENTRAL_DIRECTORY_FILE_SIGNATURE) {
|
|
16871
17013
|
throw new Error("Invalid ZIP central directory.");
|
|
16872
17014
|
}
|
|
16873
17015
|
const flags = archive.readUInt16LE(offset + 8);
|
|
16874
17016
|
const compressionMethod = archive.readUInt16LE(offset + 10);
|
|
16875
17017
|
const compressedSize = archive.readUInt32LE(offset + 20);
|
|
17018
|
+
const uncompressedSize = archive.readUInt32LE(offset + 24);
|
|
16876
17019
|
const localHeaderOffset = archive.readUInt32LE(offset + 42);
|
|
16877
17020
|
const nameLength = archive.readUInt16LE(offset + 28);
|
|
16878
17021
|
const extraLength = archive.readUInt16LE(offset + 30);
|
|
16879
17022
|
const commentLength = archive.readUInt16LE(offset + 32);
|
|
16880
17023
|
const nameStart = offset + 46;
|
|
17024
|
+
const nextOffset = nameStart + nameLength + extraLength + commentLength;
|
|
17025
|
+
if (nextOffset > endOffset) {
|
|
17026
|
+
throw new Error("Invalid ZIP central directory.");
|
|
17027
|
+
}
|
|
16881
17028
|
const name = archive.subarray(nameStart, nameStart + nameLength).toString("utf8");
|
|
16882
17029
|
entries.push({
|
|
16883
17030
|
compressedSize,
|
|
16884
17031
|
compressionMethod,
|
|
16885
17032
|
flags,
|
|
16886
17033
|
localHeaderOffset,
|
|
16887
|
-
name
|
|
17034
|
+
name,
|
|
17035
|
+
uncompressedSize
|
|
16888
17036
|
});
|
|
16889
|
-
offset =
|
|
17037
|
+
offset = nextOffset;
|
|
17038
|
+
}
|
|
17039
|
+
if (offset !== endOffset && (offset + 6 > endOffset || archive.readUInt32LE(offset) !== CENTRAL_DIRECTORY_DIGITAL_SIGNATURE || offset + 6 + archive.readUInt16LE(offset + 4) !== endOffset)) {
|
|
17040
|
+
throw new Error("Invalid ZIP central directory.");
|
|
16890
17041
|
}
|
|
16891
|
-
return entries;
|
|
17042
|
+
return { entries, offset: directoryOffset };
|
|
16892
17043
|
}
|
|
16893
17044
|
function findEndOfCentralDirectory(archive) {
|
|
16894
17045
|
const minOffset = Math.max(0, archive.length - 65557);
|
|
@@ -16899,16 +17050,19 @@ function findEndOfCentralDirectory(archive) {
|
|
|
16899
17050
|
}
|
|
16900
17051
|
throw new Error("Invalid ZIP archive.");
|
|
16901
17052
|
}
|
|
16902
|
-
function readEntryContents(archive, entry) {
|
|
17053
|
+
function readEntryContents(archive, entry, directoryOffset) {
|
|
16903
17054
|
if (entry.flags % 2 === 1) {
|
|
16904
17055
|
throw new Error("Encrypted ZIP entries are not supported.");
|
|
16905
17056
|
}
|
|
16906
|
-
if (archive.readUInt32LE(entry.localHeaderOffset) !== LOCAL_FILE_HEADER_SIGNATURE) {
|
|
17057
|
+
if (entry.localHeaderOffset + 30 > directoryOffset || archive.readUInt32LE(entry.localHeaderOffset) !== LOCAL_FILE_HEADER_SIGNATURE) {
|
|
16907
17058
|
throw new Error("Invalid ZIP local file header.");
|
|
16908
17059
|
}
|
|
16909
17060
|
const localNameLength = archive.readUInt16LE(entry.localHeaderOffset + 26);
|
|
16910
17061
|
const localExtraLength = archive.readUInt16LE(entry.localHeaderOffset + 28);
|
|
16911
17062
|
const dataStart = entry.localHeaderOffset + 30 + localNameLength + localExtraLength;
|
|
17063
|
+
if (dataStart + entry.compressedSize > directoryOffset) {
|
|
17064
|
+
throw new Error("Invalid ZIP entry data range.");
|
|
17065
|
+
}
|
|
16912
17066
|
const compressed = archive.subarray(
|
|
16913
17067
|
dataStart,
|
|
16914
17068
|
dataStart + entry.compressedSize
|
|
@@ -16919,13 +17073,20 @@ function readEntryContents(archive, entry) {
|
|
|
16919
17073
|
`ZIP entry exceeds the maximum decompressed size of ${MAX_ENTRY_DECOMPRESSED_BYTES} bytes.`
|
|
16920
17074
|
);
|
|
16921
17075
|
}
|
|
17076
|
+
if (compressed.length !== entry.uncompressedSize) {
|
|
17077
|
+
throw new Error("Invalid ZIP entry decompressed size.");
|
|
17078
|
+
}
|
|
16922
17079
|
return Buffer.from(compressed);
|
|
16923
17080
|
}
|
|
16924
17081
|
if (entry.compressionMethod === 8) {
|
|
16925
17082
|
try {
|
|
16926
|
-
|
|
17083
|
+
const contents = inflateRawSync(compressed, {
|
|
16927
17084
|
maxOutputLength: MAX_ENTRY_DECOMPRESSED_BYTES
|
|
16928
17085
|
});
|
|
17086
|
+
if (contents.length !== entry.uncompressedSize) {
|
|
17087
|
+
throw new Error("Invalid ZIP entry decompressed size.");
|
|
17088
|
+
}
|
|
17089
|
+
return contents;
|
|
16929
17090
|
} catch (error51) {
|
|
16930
17091
|
if (isMaxOutputLengthError(error51)) {
|
|
16931
17092
|
throw new Error(
|
|
@@ -16944,15 +17105,21 @@ function isMaxOutputLengthError(error51) {
|
|
|
16944
17105
|
}
|
|
16945
17106
|
function normalizeZipEntryName(name) {
|
|
16946
17107
|
const normalized = name.replaceAll("\\", "/");
|
|
16947
|
-
if (normalized.length === 0 || normalized.startsWith("/") || normalized.includes("\0")) {
|
|
16948
|
-
|
|
17108
|
+
if (normalized.length === 0 || normalized.startsWith("/") || normalized.includes(":") || normalized.includes("\0") || normalized.split("/").some(isUnsafeWindowsComponent)) {
|
|
17109
|
+
throw new Error(`Unsafe ZIP entry path: ${name}`);
|
|
16949
17110
|
}
|
|
16950
17111
|
return normalized;
|
|
16951
17112
|
}
|
|
17113
|
+
function isUnsafeWindowsComponent(component) {
|
|
17114
|
+
if (component === "." || component === "..") {
|
|
17115
|
+
return false;
|
|
17116
|
+
}
|
|
17117
|
+
return TRAILING_WINDOWS_ALIAS.test(component) || RESERVED_WINDOWS_DEVICE.test(component);
|
|
17118
|
+
}
|
|
16952
17119
|
function resolveSafeTarget(root, entryName) {
|
|
16953
17120
|
const targetPath = path2.resolve(root, entryName);
|
|
16954
17121
|
const relative = path2.relative(root, targetPath);
|
|
16955
|
-
if (relative.startsWith(
|
|
17122
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
16956
17123
|
throw new Error(`Unsafe ZIP entry path: ${entryName}`);
|
|
16957
17124
|
}
|
|
16958
17125
|
return targetPath;
|
|
@@ -16960,7 +17127,7 @@ function resolveSafeTarget(root, entryName) {
|
|
|
16960
17127
|
|
|
16961
17128
|
// src/commands/prototype-pack.ts
|
|
16962
17129
|
function registerPrototypePackCommand(program, options) {
|
|
16963
|
-
const command = program.command("prototype-pack").description("Fetch a prototype ZIP reference.").requiredOption("--prototype-id <id>", "Prototype id.").option("--out <dir>", "
|
|
17130
|
+
const command = program.command("prototype-pack").description("Fetch a prototype ZIP reference.").requiredOption("--prototype-id <id>", "Prototype id.").option("--out <dir>", "New output directory (parent must already exist).");
|
|
16964
17131
|
command.action(async (commandOptions) => {
|
|
16965
17132
|
const exitCode = await runPrototypePackCommand({
|
|
16966
17133
|
commandOptions,
|
|
@@ -17060,7 +17227,20 @@ async function fetchAndExtractPack(input) {
|
|
|
17060
17227
|
});
|
|
17061
17228
|
}
|
|
17062
17229
|
const archive = new Uint8Array(await response.arrayBuffer());
|
|
17063
|
-
|
|
17230
|
+
try {
|
|
17231
|
+
return await extractZipToDirectory(archive, input.outDir);
|
|
17232
|
+
} catch (error51) {
|
|
17233
|
+
if (error51 instanceof ZipOutputDirectoryError) {
|
|
17234
|
+
throw createCliOperationalError({
|
|
17235
|
+
code: "internal",
|
|
17236
|
+
message: error51.message
|
|
17237
|
+
});
|
|
17238
|
+
}
|
|
17239
|
+
throw createCliOperationalError({
|
|
17240
|
+
code: "internal",
|
|
17241
|
+
message: "The prototype ZIP could not be safely extracted. Check the output parent permissions and retry with a new --out directory; if the failure persists, report it."
|
|
17242
|
+
});
|
|
17243
|
+
}
|
|
17064
17244
|
}
|
|
17065
17245
|
function isSameOrigin(packUrl, endpoint) {
|
|
17066
17246
|
try {
|
|
@@ -17230,7 +17410,9 @@ function runRunOutputCommand(options) {
|
|
|
17230
17410
|
}
|
|
17231
17411
|
return runCommand(
|
|
17232
17412
|
{
|
|
17233
|
-
|
|
17413
|
+
// The tolerant consumer schema: a newer plob still reads a server that
|
|
17414
|
+
// predates the run-output size bounds.
|
|
17415
|
+
outputSchema: runOutputParseSchema,
|
|
17234
17416
|
renderHuman: renderRunOutput,
|
|
17235
17417
|
toolArguments: compactRecord({
|
|
17236
17418
|
runId: runId.value,
|
|
@@ -17245,16 +17427,69 @@ function runRunOutputCommand(options) {
|
|
|
17245
17427
|
}
|
|
17246
17428
|
);
|
|
17247
17429
|
}
|
|
17430
|
+
var OMITTED_KEY_DISPLAY_MAX = 80;
|
|
17431
|
+
var PROTOTYPE_ID_DISPLAY_MAX = 80;
|
|
17248
17432
|
function renderRunOutput(response) {
|
|
17249
17433
|
const scope = response.stage ? ` stage ${response.stage}` : "";
|
|
17250
17434
|
const lines = [`Run ${response.runId}${scope} output`];
|
|
17251
17435
|
lines.push(`Fields: ${Object.keys(response.output).length}`);
|
|
17436
|
+
const omitted = renderOmittedKeys(response);
|
|
17437
|
+
if (omitted) {
|
|
17438
|
+
lines.push(`Omitted for size: ${omitted}`);
|
|
17439
|
+
}
|
|
17440
|
+
if (response.prototypeId !== void 0) {
|
|
17441
|
+
const prototypeId = escapeTerminalText(
|
|
17442
|
+
response.prototypeId,
|
|
17443
|
+
PROTOTYPE_ID_DISPLAY_MAX
|
|
17444
|
+
);
|
|
17445
|
+
lines.push(
|
|
17446
|
+
`Prototype: ${prototypeId} (fetch with \`plob prototype-pack --prototype-id ${prototypeId}\`)`
|
|
17447
|
+
);
|
|
17448
|
+
}
|
|
17449
|
+
const edited = renderEditedStages(response);
|
|
17450
|
+
if (edited) {
|
|
17451
|
+
lines.push(
|
|
17452
|
+
`Edited by the user (read with \`plob document-get\`): ${edited}`
|
|
17453
|
+
);
|
|
17454
|
+
}
|
|
17252
17455
|
if (response.documentIds.length > 0) {
|
|
17253
|
-
lines.push(
|
|
17456
|
+
lines.push(
|
|
17457
|
+
`Documents: ${response.documentIds.map((id) => escapeTerminalText(id, DOCUMENT_ID_DISPLAY_MAX)).join(", ")}`
|
|
17458
|
+
);
|
|
17254
17459
|
}
|
|
17255
17460
|
return `${lines.join("\n")}
|
|
17256
17461
|
`;
|
|
17257
17462
|
}
|
|
17463
|
+
var DOCUMENT_ID_DISPLAY_MAX = 80;
|
|
17464
|
+
function renderEditedStages(response) {
|
|
17465
|
+
const edited = (response.stageEdits ?? []).filter(
|
|
17466
|
+
(entry) => entry.hasUserEdits
|
|
17467
|
+
);
|
|
17468
|
+
if (edited.length === 0) {
|
|
17469
|
+
return;
|
|
17470
|
+
}
|
|
17471
|
+
return edited.map((entry) => {
|
|
17472
|
+
const stage = escapeTerminalText(entry.stage, OMITTED_KEY_DISPLAY_MAX);
|
|
17473
|
+
return entry.documentId === void 0 ? stage : `${stage} (${escapeTerminalText(entry.documentId, DOCUMENT_ID_DISPLAY_MAX)})`;
|
|
17474
|
+
}).join(", ");
|
|
17475
|
+
}
|
|
17476
|
+
function renderOmittedKeys(response) {
|
|
17477
|
+
if (response.truncated !== true) {
|
|
17478
|
+
return;
|
|
17479
|
+
}
|
|
17480
|
+
const listed = (response.omittedKeys ?? []).map(
|
|
17481
|
+
(key) => escapeTerminalText(key, OMITTED_KEY_DISPLAY_MAX)
|
|
17482
|
+
);
|
|
17483
|
+
const unlisted = Math.max(
|
|
17484
|
+
0,
|
|
17485
|
+
(response.omittedKeyCount ?? listed.length) - listed.length
|
|
17486
|
+
);
|
|
17487
|
+
if (listed.length === 0) {
|
|
17488
|
+
return unlisted > 0 ? `${unlisted} ${unlisted === 1 ? "entry" : "entries"}` : void 0;
|
|
17489
|
+
}
|
|
17490
|
+
const names = listed.join(", ");
|
|
17491
|
+
return unlisted > 0 ? `${names} (and ${unlisted} more)` : names;
|
|
17492
|
+
}
|
|
17258
17493
|
|
|
17259
17494
|
// src/commands/run-replay.ts
|
|
17260
17495
|
function registerRunReplayCommand(program, options) {
|
|
@@ -17367,7 +17602,7 @@ function renderRunStatus(response) {
|
|
|
17367
17602
|
// src/commands/upload.ts
|
|
17368
17603
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
17369
17604
|
import path3 from "path";
|
|
17370
|
-
var MAX_UPLOAD_FILE_SIZE_BYTES =
|
|
17605
|
+
var MAX_UPLOAD_FILE_SIZE_BYTES = 25 * 1024 * 1024;
|
|
17371
17606
|
var DOCUMENT_UPLOAD_FIELD_NAME = "files";
|
|
17372
17607
|
var MIME_TYPES_BY_EXTENSION = /* @__PURE__ */ new Map([
|
|
17373
17608
|
[
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productlobster/plob",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "ProductLobster command-line tool — run PM analyses, manage workspaces, read run output, and upload documents from your terminal. A thin MCP client for the ProductLobster agentic surface.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "ProductLobster",
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@repo/agentic-contract": "workspace:*",
|
|
60
60
|
"@repo/typescript-config": "workspace:*",
|
|
61
|
-
"@types/node": "
|
|
62
|
-
"@vitest/coverage-v8": "^
|
|
61
|
+
"@types/node": "26.6.3",
|
|
62
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
63
63
|
"tsup": "^8.5.1",
|
|
64
64
|
"typescript": "^6.0.3",
|
|
65
|
-
"vitest": "^
|
|
65
|
+
"vitest": "^5.0.3"
|
|
66
66
|
}
|
|
67
67
|
}
|