gsd-pi 2.7.1 → 2.8.1
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 +12 -5
- package/dist/loader.js +0 -0
- package/dist/modes/interactive/theme/dark.json +85 -0
- package/dist/modes/interactive/theme/light.json +84 -0
- package/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/dist/modes/interactive/theme/theme.d.ts +78 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme.js +949 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/config.js +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js +117 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js +97 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js +112 -3
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js +30 -20
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js +3 -0
- package/node_modules/@gsd/pi-coding-agent/dist/index.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/config.ts +5 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/artifact-manager.ts +125 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/blob-store.ts +106 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/session-manager.ts +119 -3
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/bash.ts +33 -20
- package/node_modules/@gsd/pi-coding-agent/src/index.ts +3 -0
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
- package/node_modules/cliui/CHANGELOG.md +121 -0
- package/node_modules/color-convert/CHANGELOG.md +54 -0
- package/node_modules/esprima/ChangeLog +235 -0
- package/node_modules/mz/HISTORY.md +66 -0
- package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
- package/node_modules/source-map/CHANGELOG.md +301 -0
- package/node_modules/thenify/History.md +11 -0
- package/node_modules/thenify-all/History.md +11 -0
- package/node_modules/y18n/CHANGELOG.md +100 -0
- package/node_modules/yargs/CHANGELOG.md +88 -0
- package/node_modules/yargs-parser/CHANGELOG.md +263 -0
- package/package.json +10 -2
- package/packages/pi-coding-agent/dist/config.d.ts +2 -0
- package/packages/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/config.js +4 -0
- package/packages/pi-coding-agent/dist/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.js +117 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/blob-store.js +97 -0
- package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +112 -3
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +30 -20
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +3 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/config.ts +5 -0
- package/packages/pi-coding-agent/src/core/artifact-manager.ts +125 -0
- package/packages/pi-coding-agent/src/core/blob-store.ts +106 -0
- package/packages/pi-coding-agent/src/core/session-manager.ts +119 -3
- package/packages/pi-coding-agent/src/core/tools/bash.ts +33 -20
- package/packages/pi-coding-agent/src/index.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
- package/src/resources/extensions/browser-tools/capture.ts +165 -0
- package/src/resources/extensions/browser-tools/evaluate-helpers.ts +184 -0
- package/src/resources/extensions/browser-tools/index.ts +47 -4985
- package/src/resources/extensions/browser-tools/lifecycle.ts +270 -0
- package/src/resources/extensions/browser-tools/package.json +5 -1
- package/src/resources/extensions/browser-tools/refs.ts +264 -0
- package/src/resources/extensions/browser-tools/settle.ts +197 -0
- package/src/resources/extensions/browser-tools/state.ts +408 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +652 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +614 -0
- package/src/resources/extensions/browser-tools/tools/assertions.ts +342 -0
- package/src/resources/extensions/browser-tools/tools/forms.ts +801 -0
- package/src/resources/extensions/browser-tools/tools/inspection.ts +492 -0
- package/src/resources/extensions/browser-tools/tools/intent.ts +614 -0
- package/src/resources/extensions/browser-tools/tools/interaction.ts +865 -0
- package/src/resources/extensions/browser-tools/tools/navigation.ts +232 -0
- package/src/resources/extensions/browser-tools/tools/pages.ts +303 -0
- package/src/resources/extensions/browser-tools/tools/refs.ts +541 -0
- package/src/resources/extensions/browser-tools/tools/screenshot.ts +83 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +400 -0
- package/src/resources/extensions/browser-tools/tools/wait.ts +247 -0
- package/src/resources/extensions/browser-tools/utils.ts +660 -0
- package/src/resources/extensions/gsd/auto.ts +74 -13
- package/src/resources/extensions/gsd/dispatch-guard.ts +65 -0
- package/src/resources/extensions/gsd/exit-command.ts +18 -0
- package/src/resources/extensions/gsd/files.ts +9 -40
- package/src/resources/extensions/gsd/git-service.ts +65 -17
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +39 -2
- package/src/resources/extensions/gsd/index.ts +111 -16
- package/src/resources/extensions/gsd/preferences.ts +8 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +25 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +50 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +64 -39
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
- package/src/resources/extensions/shared/interview-ui.ts +1 -1
- package/src/resources/extensions/ttsr/index.ts +163 -0
- package/src/resources/extensions/ttsr/rule-loader.ts +121 -0
- package/src/resources/extensions/ttsr/ttsr-interrupt.md +6 -0
- package/src/resources/extensions/ttsr/ttsr-manager.ts +344 -0
|
@@ -235,6 +235,11 @@ export function getSessionsDir(): string {
|
|
|
235
235
|
return join(getAgentDir(), "sessions");
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
/** Get path to content-addressed blob store directory */
|
|
239
|
+
export function getBlobsDir(): string {
|
|
240
|
+
return join(getAgentDir(), "blobs");
|
|
241
|
+
}
|
|
242
|
+
|
|
238
243
|
/** Get path to debug log file */
|
|
239
244
|
export function getDebugLogPath(): string {
|
|
240
245
|
return join(getAgentDir(), `${APP_NAME}-debug.log`);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped artifact storage for truncated tool outputs.
|
|
3
|
+
*
|
|
4
|
+
* Artifacts are stored in a directory alongside the session file,
|
|
5
|
+
* accessible via artifact:// URLs.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdirSync, readdirSync, writeFileSync, existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Manages artifact storage for a session.
|
|
12
|
+
*
|
|
13
|
+
* Artifacts are stored with sequential IDs in the session's artifact directory.
|
|
14
|
+
* The directory is created lazily on first write.
|
|
15
|
+
*/
|
|
16
|
+
export class ArtifactManager {
|
|
17
|
+
#nextId = 0;
|
|
18
|
+
readonly #dir: string;
|
|
19
|
+
#dirCreated = false;
|
|
20
|
+
#initialized = false;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param sessionFile Path to the session .jsonl file
|
|
24
|
+
*/
|
|
25
|
+
constructor(sessionFile: string) {
|
|
26
|
+
// Artifact directory is session file path without .jsonl extension
|
|
27
|
+
this.#dir = sessionFile.slice(0, -6);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Artifact directory path.
|
|
32
|
+
* Directory may not exist until first artifact is saved.
|
|
33
|
+
*/
|
|
34
|
+
get dir(): string {
|
|
35
|
+
return this.#dir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#ensureDir(): void {
|
|
39
|
+
if (!this.#dirCreated) {
|
|
40
|
+
mkdirSync(this.#dir, { recursive: true });
|
|
41
|
+
this.#dirCreated = true;
|
|
42
|
+
}
|
|
43
|
+
if (!this.#initialized) {
|
|
44
|
+
this.#scanExistingIds();
|
|
45
|
+
this.#initialized = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Scan existing artifact files to find the next available ID.
|
|
51
|
+
* Ensures we don't overwrite artifacts when resuming a session.
|
|
52
|
+
*/
|
|
53
|
+
#scanExistingIds(): void {
|
|
54
|
+
const files = this.listFiles();
|
|
55
|
+
let maxId = -1;
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
const match = file.match(/^(\d+)\..*\.log$/);
|
|
58
|
+
if (match) {
|
|
59
|
+
const id = parseInt(match[1], 10);
|
|
60
|
+
if (id > maxId) maxId = id;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
this.#nextId = maxId + 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Atomically allocate next artifact ID. */
|
|
67
|
+
allocateId(): number {
|
|
68
|
+
return this.#nextId++;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Allocate a new artifact path and ID without writing content.
|
|
73
|
+
* @param toolType Tool name for file extension (e.g., "bash", "fetch")
|
|
74
|
+
*/
|
|
75
|
+
allocatePath(toolType: string): { id: string; path: string } {
|
|
76
|
+
this.#ensureDir();
|
|
77
|
+
const id = String(this.allocateId());
|
|
78
|
+
const filename = `${id}.${toolType}.log`;
|
|
79
|
+
return { id, path: join(this.#dir, filename) };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Save content as an artifact and return the artifact ID.
|
|
84
|
+
* @param content Full content to save
|
|
85
|
+
* @param toolType Tool name for file extension (e.g., "bash", "fetch")
|
|
86
|
+
* @returns Artifact ID (numeric string)
|
|
87
|
+
*/
|
|
88
|
+
save(content: string, toolType: string): string {
|
|
89
|
+
const { id, path } = this.allocatePath(toolType);
|
|
90
|
+
writeFileSync(path, content);
|
|
91
|
+
return id;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if an artifact exists.
|
|
96
|
+
* @param id Artifact ID (numeric string)
|
|
97
|
+
*/
|
|
98
|
+
exists(id: string): boolean {
|
|
99
|
+
const files = this.listFiles();
|
|
100
|
+
return files.some((f) => f.startsWith(`${id}.`));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* List all artifact files in the directory.
|
|
105
|
+
* Returns empty array if directory doesn't exist.
|
|
106
|
+
*/
|
|
107
|
+
listFiles(): string[] {
|
|
108
|
+
try {
|
|
109
|
+
return readdirSync(this.#dir);
|
|
110
|
+
} catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get the full path to an artifact file.
|
|
117
|
+
* Returns null if artifact doesn't exist.
|
|
118
|
+
* @param id Artifact ID (numeric string)
|
|
119
|
+
*/
|
|
120
|
+
getPath(id: string): string | null {
|
|
121
|
+
const files = this.listFiles();
|
|
122
|
+
const match = files.find((f) => f.startsWith(`${id}.`));
|
|
123
|
+
return match ? join(this.#dir, match) : null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-addressed blob store for externalizing large binary data (images) from session JSONL files.
|
|
3
|
+
*
|
|
4
|
+
* Files are stored at `<dir>/<sha256-hex>` with no extension. The SHA-256 hash is computed
|
|
5
|
+
* over the raw binary data (not base64). Content-addressing makes writes idempotent and
|
|
6
|
+
* provides automatic deduplication across sessions.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, accessSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
const BLOB_PREFIX = "blob:sha256:";
|
|
13
|
+
const SHA256_HEX_RE = /^[a-f0-9]{64}$/;
|
|
14
|
+
|
|
15
|
+
export interface BlobPutResult {
|
|
16
|
+
hash: string;
|
|
17
|
+
path: string;
|
|
18
|
+
get ref(): string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class BlobStore {
|
|
22
|
+
readonly dir: string;
|
|
23
|
+
constructor(dir: string) {
|
|
24
|
+
this.dir = dir;
|
|
25
|
+
mkdirSync(dir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Write binary data to the blob store. Idempotent — same content → same hash. */
|
|
29
|
+
put(data: Buffer): BlobPutResult {
|
|
30
|
+
const hash = createHash("sha256").update(data).digest("hex");
|
|
31
|
+
const blobPath = join(this.dir, hash);
|
|
32
|
+
const result: BlobPutResult = {
|
|
33
|
+
hash,
|
|
34
|
+
path: blobPath,
|
|
35
|
+
get ref() {
|
|
36
|
+
return `${BLOB_PREFIX}${hash}`;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (!existsSync(blobPath)) {
|
|
41
|
+
writeFileSync(blobPath, data);
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Read blob by hash, returns Buffer or null if not found. */
|
|
47
|
+
get(hash: string): Buffer | null {
|
|
48
|
+
if (!SHA256_HEX_RE.test(hash)) return null;
|
|
49
|
+
const blobPath = join(this.dir, hash);
|
|
50
|
+
try {
|
|
51
|
+
return readFileSync(blobPath);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Check if a blob exists. */
|
|
58
|
+
has(hash: string): boolean {
|
|
59
|
+
if (!SHA256_HEX_RE.test(hash)) return false;
|
|
60
|
+
try {
|
|
61
|
+
accessSync(join(this.dir, hash));
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Check if a data string is a blob reference. */
|
|
70
|
+
export function isBlobRef(data: string): boolean {
|
|
71
|
+
return data.startsWith(BLOB_PREFIX);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Extract the SHA-256 hash from a blob reference string. Returns null if format is invalid. */
|
|
75
|
+
export function parseBlobRef(data: string): string | null {
|
|
76
|
+
if (!data.startsWith(BLOB_PREFIX)) return null;
|
|
77
|
+
const hash = data.slice(BLOB_PREFIX.length);
|
|
78
|
+
if (!SHA256_HEX_RE.test(hash)) return null;
|
|
79
|
+
return hash;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Externalize an image's base64 data to the blob store, returning a blob reference.
|
|
84
|
+
* If the data is already a blob reference, returns it unchanged.
|
|
85
|
+
*/
|
|
86
|
+
export function externalizeImageData(blobStore: BlobStore, base64Data: string): string {
|
|
87
|
+
if (isBlobRef(base64Data)) return base64Data;
|
|
88
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
89
|
+
const { ref } = blobStore.put(buffer);
|
|
90
|
+
return ref;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resolve a blob reference back to base64 data.
|
|
95
|
+
* If the data is not a blob reference, returns it unchanged.
|
|
96
|
+
* If the blob is missing, returns the ref unchanged.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveImageData(blobStore: BlobStore, data: string): string {
|
|
99
|
+
const hash = parseBlobRef(data);
|
|
100
|
+
if (!hash) return data;
|
|
101
|
+
|
|
102
|
+
const buffer = blobStore.get(hash);
|
|
103
|
+
if (!buffer) return data; // Missing blob — return ref as-is
|
|
104
|
+
|
|
105
|
+
return buffer.toString("base64");
|
|
106
|
+
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "fs";
|
|
16
16
|
import { readdir, readFile, stat } from "fs/promises";
|
|
17
17
|
import { join, resolve } from "path";
|
|
18
|
-
import { getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
|
|
18
|
+
import { getAgentDir as getDefaultAgentDir, getBlobsDir, getSessionsDir } from "../config.js";
|
|
19
19
|
import {
|
|
20
20
|
type BashExecutionMessage,
|
|
21
21
|
type CustomMessage,
|
|
@@ -23,6 +23,11 @@ import {
|
|
|
23
23
|
createCompactionSummaryMessage,
|
|
24
24
|
createCustomMessage,
|
|
25
25
|
} from "./messages.js";
|
|
26
|
+
import { BlobStore, externalizeImageData, isBlobRef, resolveImageData } from "./blob-store.js";
|
|
27
|
+
|
|
28
|
+
const BLOB_EXTERNALIZE_THRESHOLD = 1024; // 1KB minimum to externalize
|
|
29
|
+
const MAX_PERSIST_CHARS = 500_000;
|
|
30
|
+
const TRUNCATION_NOTICE = "\n\n[Session persistence truncated large content]";
|
|
26
31
|
|
|
27
32
|
export const CURRENT_SESSION_VERSION = 3;
|
|
28
33
|
|
|
@@ -426,6 +431,112 @@ function getDefaultSessionDir(cwd: string): string {
|
|
|
426
431
|
return sessionDir;
|
|
427
432
|
}
|
|
428
433
|
|
|
434
|
+
function isImageBlock(value: unknown): value is { type: "image"; data: string; mimeType?: string } {
|
|
435
|
+
return (
|
|
436
|
+
typeof value === "object" &&
|
|
437
|
+
value !== null &&
|
|
438
|
+
"type" in value &&
|
|
439
|
+
(value as { type?: string }).type === "image" &&
|
|
440
|
+
"data" in value &&
|
|
441
|
+
typeof (value as { data?: string }).data === "string"
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function truncateString(s: string, maxLength: number): string {
|
|
446
|
+
if (s.length <= maxLength) return s;
|
|
447
|
+
// Avoid splitting surrogate pairs
|
|
448
|
+
if (maxLength > 0 && s.charCodeAt(maxLength - 1) >= 0xd800 && s.charCodeAt(maxLength - 1) <= 0xdbff) {
|
|
449
|
+
return s.slice(0, maxLength - 1);
|
|
450
|
+
}
|
|
451
|
+
return s.slice(0, maxLength);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Prepare an entry for JSONL persistence: externalize large images to blob store,
|
|
456
|
+
* truncate oversized strings, strip transient fields.
|
|
457
|
+
*/
|
|
458
|
+
function prepareForPersistence(obj: unknown, blobStore: BlobStore, key?: string): unknown {
|
|
459
|
+
if (obj === null || obj === undefined) return obj;
|
|
460
|
+
|
|
461
|
+
if (typeof obj === "string") {
|
|
462
|
+
if (obj.length > MAX_PERSIST_CHARS) {
|
|
463
|
+
// Cryptographic signatures must be preserved exactly or cleared entirely
|
|
464
|
+
if (key === "thinkingSignature" || key === "thoughtSignature" || key === "textSignature") {
|
|
465
|
+
return "";
|
|
466
|
+
}
|
|
467
|
+
const limit = Math.max(0, MAX_PERSIST_CHARS - TRUNCATION_NOTICE.length);
|
|
468
|
+
return `${truncateString(obj, limit)}${TRUNCATION_NOTICE}`;
|
|
469
|
+
}
|
|
470
|
+
return obj;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (Array.isArray(obj)) {
|
|
474
|
+
let changed = false;
|
|
475
|
+
const result = obj.map((item) => {
|
|
476
|
+
// Externalize oversized images to blob store
|
|
477
|
+
if (key === "content" && isImageBlock(item)) {
|
|
478
|
+
if (!isBlobRef(item.data) && item.data.length >= BLOB_EXTERNALIZE_THRESHOLD) {
|
|
479
|
+
changed = true;
|
|
480
|
+
const blobRef = externalizeImageData(blobStore, item.data);
|
|
481
|
+
return { ...item, data: blobRef };
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const newItem = prepareForPersistence(item, blobStore, key);
|
|
485
|
+
if (newItem !== item) changed = true;
|
|
486
|
+
return newItem;
|
|
487
|
+
});
|
|
488
|
+
return changed ? result : obj;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (typeof obj === "object") {
|
|
492
|
+
let changed = false;
|
|
493
|
+
const result: Record<string, unknown> = {};
|
|
494
|
+
for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
|
|
495
|
+
// Strip transient properties
|
|
496
|
+
if (k === "partialJson" || k === "jsonlEvents") {
|
|
497
|
+
changed = true;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
const newV = prepareForPersistence(v, blobStore, k);
|
|
501
|
+
result[k] = newV;
|
|
502
|
+
if (newV !== v) changed = true;
|
|
503
|
+
}
|
|
504
|
+
// Update lineCount if content was truncated (for FileMentionFile)
|
|
505
|
+
if (changed && "lineCount" in result && "content" in result && typeof result.content === "string") {
|
|
506
|
+
result.lineCount = (result.content as string).split("\n").length;
|
|
507
|
+
}
|
|
508
|
+
return changed ? result : obj;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return obj;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Resolve blob references in loaded entries, replacing `blob:sha256:<hash>` data
|
|
516
|
+
* fields with actual base64 content. Mutates entries in place.
|
|
517
|
+
*/
|
|
518
|
+
function resolveBlobRefsInEntries(entries: FileEntry[], blobStore: BlobStore): void {
|
|
519
|
+
for (const entry of entries) {
|
|
520
|
+
if (entry.type === "session") continue;
|
|
521
|
+
|
|
522
|
+
let contentArray: unknown[] | undefined;
|
|
523
|
+
if (entry.type === "message") {
|
|
524
|
+
const content = ((entry as SessionMessageEntry).message as { content?: unknown }).content;
|
|
525
|
+
if (Array.isArray(content)) contentArray = content;
|
|
526
|
+
} else if (entry.type === "custom_message" && Array.isArray((entry as any).content)) {
|
|
527
|
+
contentArray = (entry as any).content;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (!contentArray) continue;
|
|
531
|
+
|
|
532
|
+
for (const block of contentArray) {
|
|
533
|
+
if (isImageBlock(block) && isBlobRef(block.data)) {
|
|
534
|
+
(block as { data: string }).data = resolveImageData(blobStore, block.data);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
429
540
|
/** Exported for testing */
|
|
430
541
|
export function loadEntriesFromFile(filePath: string): FileEntry[] {
|
|
431
542
|
if (!existsSync(filePath)) return [];
|
|
@@ -669,6 +780,7 @@ export class SessionManager {
|
|
|
669
780
|
private flushed: boolean = false;
|
|
670
781
|
private fileEntries: FileEntry[] = [];
|
|
671
782
|
private byId: Map<string, SessionEntry> = new Map();
|
|
783
|
+
private blobStore: BlobStore;
|
|
672
784
|
private labelsById: Map<string, string> = new Map();
|
|
673
785
|
private leafId: string | null = null;
|
|
674
786
|
|
|
@@ -676,6 +788,7 @@ export class SessionManager {
|
|
|
676
788
|
this.cwd = cwd;
|
|
677
789
|
this.sessionDir = sessionDir;
|
|
678
790
|
this.persist = persist;
|
|
791
|
+
this.blobStore = new BlobStore(getBlobsDir());
|
|
679
792
|
if (persist && sessionDir && !existsSync(sessionDir)) {
|
|
680
793
|
mkdirSync(sessionDir, { recursive: true });
|
|
681
794
|
}
|
|
@@ -712,6 +825,7 @@ export class SessionManager {
|
|
|
712
825
|
}
|
|
713
826
|
|
|
714
827
|
this._buildIndex();
|
|
828
|
+
resolveBlobRefsInEntries(this.fileEntries, this.blobStore);
|
|
715
829
|
this.flushed = true;
|
|
716
830
|
} else {
|
|
717
831
|
const explicitPath = this.sessionFile;
|
|
@@ -800,11 +914,13 @@ export class SessionManager {
|
|
|
800
914
|
|
|
801
915
|
if (!this.flushed) {
|
|
802
916
|
for (const e of this.fileEntries) {
|
|
803
|
-
|
|
917
|
+
const prepared = prepareForPersistence(e, this.blobStore) as FileEntry;
|
|
918
|
+
appendFileSync(this.sessionFile, `${JSON.stringify(prepared)}\n`);
|
|
804
919
|
}
|
|
805
920
|
this.flushed = true;
|
|
806
921
|
} else {
|
|
807
|
-
|
|
922
|
+
const prepared = prepareForPersistence(entry, this.blobStore) as FileEntry;
|
|
923
|
+
appendFileSync(this.sessionFile, `${JSON.stringify(prepared)}\n`);
|
|
808
924
|
}
|
|
809
925
|
}
|
|
810
926
|
|
|
@@ -8,6 +8,7 @@ import { type Static, Type } from "@sinclair/typebox";
|
|
|
8
8
|
import { spawn } from "child_process";
|
|
9
9
|
import { getShellConfig, getShellEnv, killProcessTree } from "../../utils/shell.js";
|
|
10
10
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from "./truncate.js";
|
|
11
|
+
import type { ArtifactManager } from "../artifact-manager.js";
|
|
11
12
|
|
|
12
13
|
// Cached Win32 FFI handles for restoring VT input after child processes
|
|
13
14
|
let _vtHandles: { GetConsoleMode: any; SetConsoleMode: any; handle: any } | null = null;
|
|
@@ -51,6 +52,7 @@ export type BashToolInput = Static<typeof bashSchema>;
|
|
|
51
52
|
export interface BashToolDetails {
|
|
52
53
|
truncation?: TruncationResult;
|
|
53
54
|
fullOutputPath?: string;
|
|
55
|
+
artifactId?: string;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
@@ -187,12 +189,15 @@ export interface BashToolOptions {
|
|
|
187
189
|
commandPrefix?: string;
|
|
188
190
|
/** Hook to adjust command, cwd, or env before execution */
|
|
189
191
|
spawnHook?: BashSpawnHook;
|
|
192
|
+
/** Session-scoped artifact storage. When provided, spills to artifact files instead of temp files. */
|
|
193
|
+
artifactManager?: ArtifactManager;
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
export function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {
|
|
193
197
|
const ops = options?.operations ?? defaultBashOperations;
|
|
194
198
|
const commandPrefix = options?.commandPrefix;
|
|
195
199
|
const spawnHook = options?.spawnHook;
|
|
200
|
+
const artifactManager = options?.artifactManager;
|
|
196
201
|
|
|
197
202
|
return {
|
|
198
203
|
name: "bash",
|
|
@@ -210,9 +215,10 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
210
215
|
const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);
|
|
211
216
|
|
|
212
217
|
return new Promise((resolve, reject) => {
|
|
213
|
-
// We'll stream to a
|
|
214
|
-
let
|
|
215
|
-
let
|
|
218
|
+
// We'll stream to a file if output gets large
|
|
219
|
+
let spillFilePath: string | undefined;
|
|
220
|
+
let spillArtifactId: string | undefined;
|
|
221
|
+
let spillFileStream: ReturnType<typeof createWriteStream> | undefined;
|
|
216
222
|
let totalBytes = 0;
|
|
217
223
|
|
|
218
224
|
// Keep a rolling buffer of the last chunk for tail truncation
|
|
@@ -224,19 +230,25 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
224
230
|
const handleData = (data: Buffer) => {
|
|
225
231
|
totalBytes += data.length;
|
|
226
232
|
|
|
227
|
-
// Start writing to
|
|
228
|
-
if (totalBytes > DEFAULT_MAX_BYTES && !
|
|
229
|
-
|
|
230
|
-
|
|
233
|
+
// Start writing to file once we exceed the threshold
|
|
234
|
+
if (totalBytes > DEFAULT_MAX_BYTES && !spillFilePath) {
|
|
235
|
+
if (artifactManager) {
|
|
236
|
+
const allocated = artifactManager.allocatePath("bash");
|
|
237
|
+
spillFilePath = allocated.path;
|
|
238
|
+
spillArtifactId = allocated.id;
|
|
239
|
+
} else {
|
|
240
|
+
spillFilePath = getTempFilePath();
|
|
241
|
+
}
|
|
242
|
+
spillFileStream = createWriteStream(spillFilePath);
|
|
231
243
|
// Write all buffered chunks to the file
|
|
232
244
|
for (const chunk of chunks) {
|
|
233
|
-
|
|
245
|
+
spillFileStream.write(chunk);
|
|
234
246
|
}
|
|
235
247
|
}
|
|
236
248
|
|
|
237
249
|
// Write to temp file if we have one
|
|
238
|
-
if (
|
|
239
|
-
|
|
250
|
+
if (spillFileStream) {
|
|
251
|
+
spillFileStream.write(data);
|
|
240
252
|
}
|
|
241
253
|
|
|
242
254
|
// Keep rolling buffer of recent data
|
|
@@ -258,7 +270,7 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
258
270
|
content: [{ type: "text", text: truncation.content || "" }],
|
|
259
271
|
details: {
|
|
260
272
|
truncation: truncation.truncated ? truncation : undefined,
|
|
261
|
-
fullOutputPath:
|
|
273
|
+
fullOutputPath: spillFilePath,
|
|
262
274
|
},
|
|
263
275
|
});
|
|
264
276
|
}
|
|
@@ -272,8 +284,8 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
272
284
|
})
|
|
273
285
|
.then(({ exitCode }) => {
|
|
274
286
|
// Close temp file stream
|
|
275
|
-
if (
|
|
276
|
-
|
|
287
|
+
if (spillFileStream) {
|
|
288
|
+
spillFileStream.end();
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
// Combine all buffered chunks
|
|
@@ -290,21 +302,22 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
290
302
|
if (truncation.truncated) {
|
|
291
303
|
details = {
|
|
292
304
|
truncation,
|
|
293
|
-
fullOutputPath:
|
|
305
|
+
fullOutputPath: spillFilePath,
|
|
306
|
+
...(spillArtifactId ? { artifactId: spillArtifactId } : {}),
|
|
294
307
|
};
|
|
295
308
|
|
|
296
309
|
// Build actionable notice
|
|
297
310
|
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
298
311
|
const endLine = truncation.totalLines;
|
|
312
|
+
const outputRef = spillArtifactId ? `artifact://${spillArtifactId}` : spillFilePath;
|
|
299
313
|
|
|
300
314
|
if (truncation.lastLinePartial) {
|
|
301
|
-
// Edge case: last line alone > 30KB
|
|
302
315
|
const lastLineSize = formatSize(Buffer.byteLength(fullOutput.split("\n").pop() || "", "utf-8"));
|
|
303
|
-
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${
|
|
316
|
+
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${outputRef}]`;
|
|
304
317
|
} else if (truncation.truncatedBy === "lines") {
|
|
305
|
-
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${
|
|
318
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${outputRef}]`;
|
|
306
319
|
} else {
|
|
307
|
-
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${
|
|
320
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${outputRef}]`;
|
|
308
321
|
}
|
|
309
322
|
}
|
|
310
323
|
|
|
@@ -317,8 +330,8 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
|
|
|
317
330
|
})
|
|
318
331
|
.catch((err: Error) => {
|
|
319
332
|
// Close temp file stream
|
|
320
|
-
if (
|
|
321
|
-
|
|
333
|
+
if (spillFileStream) {
|
|
334
|
+
spillFileStream.end();
|
|
322
335
|
}
|
|
323
336
|
|
|
324
337
|
// Combine all buffered chunks for error output
|
|
@@ -198,6 +198,9 @@ export {
|
|
|
198
198
|
type SessionMessageEntry,
|
|
199
199
|
type ThinkingLevelChangeEntry,
|
|
200
200
|
} from "./core/session-manager.js";
|
|
201
|
+
// Blob and artifact storage
|
|
202
|
+
export { BlobStore, isBlobRef, parseBlobRef, externalizeImageData, resolveImageData } from "./core/blob-store.js";
|
|
203
|
+
export { ArtifactManager } from "./core/artifact-manager.js";
|
|
201
204
|
export {
|
|
202
205
|
type CompactionSettings,
|
|
203
206
|
type ImageSettings,
|
|
@@ -176,7 +176,7 @@ export class InteractiveMode {
|
|
|
176
176
|
private pendingTools = new Map<string, ToolExecutionComponent>();
|
|
177
177
|
|
|
178
178
|
// Tool output expansion state
|
|
179
|
-
private toolOutputExpanded =
|
|
179
|
+
private toolOutputExpanded = true;
|
|
180
180
|
|
|
181
181
|
// Thinking block visibility state
|
|
182
182
|
private hideThinkingBlock = false;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
### [7.0.4](https://www.github.com/yargs/cliui/compare/v7.0.3...v7.0.4) (2020-11-08)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **deno:** import UIOptions from definitions ([#97](https://www.github.com/yargs/cliui/issues/97)) ([f04f343](https://www.github.com/yargs/cliui/commit/f04f3439bc78114c7e90f82ff56f5acf16268ea8))
|
|
11
|
+
|
|
12
|
+
### [7.0.3](https://www.github.com/yargs/cliui/compare/v7.0.2...v7.0.3) (2020-10-16)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **exports:** node 13.0 and 13.1 require the dotted object form _with_ a string fallback ([#93](https://www.github.com/yargs/cliui/issues/93)) ([eca16fc](https://www.github.com/yargs/cliui/commit/eca16fc05d26255df3280906c36d7f0e5b05c6e9))
|
|
18
|
+
|
|
19
|
+
### [7.0.2](https://www.github.com/yargs/cliui/compare/v7.0.1...v7.0.2) (2020-10-14)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* **exports:** node 13.0-13.6 require a string fallback ([#91](https://www.github.com/yargs/cliui/issues/91)) ([b529d7e](https://www.github.com/yargs/cliui/commit/b529d7e432901af1af7848b23ed6cf634497d961))
|
|
25
|
+
|
|
26
|
+
### [7.0.1](https://www.github.com/yargs/cliui/compare/v7.0.0...v7.0.1) (2020-08-16)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
* **build:** main should be build/index.cjs ([dc29a3c](https://www.github.com/yargs/cliui/commit/dc29a3cc617a410aa850e06337b5954b04f2cb4d))
|
|
32
|
+
|
|
33
|
+
## [7.0.0](https://www.github.com/yargs/cliui/compare/v6.0.0...v7.0.0) (2020-08-16)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### ⚠ BREAKING CHANGES
|
|
37
|
+
|
|
38
|
+
* tsc/ESM/Deno support (#82)
|
|
39
|
+
* modernize deps and build (#80)
|
|
40
|
+
|
|
41
|
+
### Build System
|
|
42
|
+
|
|
43
|
+
* modernize deps and build ([#80](https://www.github.com/yargs/cliui/issues/80)) ([339d08d](https://www.github.com/yargs/cliui/commit/339d08dc71b15a3928aeab09042af94db2f43743))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Code Refactoring
|
|
47
|
+
|
|
48
|
+
* tsc/ESM/Deno support ([#82](https://www.github.com/yargs/cliui/issues/82)) ([4b777a5](https://www.github.com/yargs/cliui/commit/4b777a5fe01c5d8958c6708695d6aab7dbe5706c))
|
|
49
|
+
|
|
50
|
+
## [6.0.0](https://www.github.com/yargs/cliui/compare/v5.0.0...v6.0.0) (2019-11-10)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### ⚠ BREAKING CHANGES
|
|
54
|
+
|
|
55
|
+
* update deps, drop Node 6
|
|
56
|
+
|
|
57
|
+
### Code Refactoring
|
|
58
|
+
|
|
59
|
+
* update deps, drop Node 6 ([62056df](https://www.github.com/yargs/cliui/commit/62056df))
|
|
60
|
+
|
|
61
|
+
## [5.0.0](https://github.com/yargs/cliui/compare/v4.1.0...v5.0.0) (2019-04-10)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
### Bug Fixes
|
|
65
|
+
|
|
66
|
+
* Update wrap-ansi to fix compatibility with latest versions of chalk. ([#60](https://github.com/yargs/cliui/issues/60)) ([7bf79ae](https://github.com/yargs/cliui/commit/7bf79ae))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
### BREAKING CHANGES
|
|
70
|
+
|
|
71
|
+
* Drop support for node < 6.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
<a name="4.1.0"></a>
|
|
76
|
+
## [4.1.0](https://github.com/yargs/cliui/compare/v4.0.0...v4.1.0) (2018-04-23)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
### Features
|
|
80
|
+
|
|
81
|
+
* add resetOutput method ([#57](https://github.com/yargs/cliui/issues/57)) ([7246902](https://github.com/yargs/cliui/commit/7246902))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
<a name="4.0.0"></a>
|
|
86
|
+
## [4.0.0](https://github.com/yargs/cliui/compare/v3.2.0...v4.0.0) (2017-12-18)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
### Bug Fixes
|
|
90
|
+
|
|
91
|
+
* downgrades strip-ansi to version 3.0.1 ([#54](https://github.com/yargs/cliui/issues/54)) ([5764c46](https://github.com/yargs/cliui/commit/5764c46))
|
|
92
|
+
* set env variable FORCE_COLOR. ([#56](https://github.com/yargs/cliui/issues/56)) ([7350e36](https://github.com/yargs/cliui/commit/7350e36))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
### Chores
|
|
96
|
+
|
|
97
|
+
* drop support for node < 4 ([#53](https://github.com/yargs/cliui/issues/53)) ([b105376](https://github.com/yargs/cliui/commit/b105376))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
### Features
|
|
101
|
+
|
|
102
|
+
* add fallback for window width ([#45](https://github.com/yargs/cliui/issues/45)) ([d064922](https://github.com/yargs/cliui/commit/d064922))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
### BREAKING CHANGES
|
|
106
|
+
|
|
107
|
+
* officially drop support for Node < 4
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
<a name="3.2.0"></a>
|
|
112
|
+
## [3.2.0](https://github.com/yargs/cliui/compare/v3.1.2...v3.2.0) (2016-04-11)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
### Bug Fixes
|
|
116
|
+
|
|
117
|
+
* reduces tarball size ([acc6c33](https://github.com/yargs/cliui/commit/acc6c33))
|
|
118
|
+
|
|
119
|
+
### Features
|
|
120
|
+
|
|
121
|
+
* adds standard-version for release management ([ff84e32](https://github.com/yargs/cliui/commit/ff84e32))
|