@hobin/developer 0.1.9 → 0.1.10
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/extensions/compaction-language.ts +303 -0
- package/extensions/developer.ts +74 -2
- package/package.json +1 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
export const COMPACTION_LANGUAGE_PROTOCOL =
|
|
2
|
+
"developer.compaction-language/v1" as const;
|
|
3
|
+
export const COMPACTION_LANGUAGE_ENTRY =
|
|
4
|
+
"developer.compaction-language" as const;
|
|
5
|
+
export const COMPACTION_LANGUAGE_MESSAGE =
|
|
6
|
+
"developer.compaction-language-continuity" as const;
|
|
7
|
+
|
|
8
|
+
export type LanguageTag = string;
|
|
9
|
+
|
|
10
|
+
export interface LanguageObserved {
|
|
11
|
+
protocol: typeof COMPACTION_LANGUAGE_PROTOCOL;
|
|
12
|
+
kind: "language-observed";
|
|
13
|
+
tag: LanguageTag;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ContinuityPending {
|
|
17
|
+
protocol: typeof COMPACTION_LANGUAGE_PROTOCOL;
|
|
18
|
+
kind: "continuity-pending";
|
|
19
|
+
compactionId: string;
|
|
20
|
+
tag: LanguageTag;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ContinuityConsumed {
|
|
24
|
+
protocol: typeof COMPACTION_LANGUAGE_PROTOCOL;
|
|
25
|
+
kind: "continuity-consumed";
|
|
26
|
+
compactionId: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type CompactionLanguageEvent =
|
|
30
|
+
| LanguageObserved
|
|
31
|
+
| ContinuityPending
|
|
32
|
+
| ContinuityConsumed;
|
|
33
|
+
|
|
34
|
+
export interface PendingContinuity {
|
|
35
|
+
compactionId: string;
|
|
36
|
+
tag: LanguageTag;
|
|
37
|
+
injected: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CompactionLanguageState {
|
|
41
|
+
language?: LanguageTag;
|
|
42
|
+
pending?: PendingContinuity;
|
|
43
|
+
consumedCompactionIds: ReadonlySet<string>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ContinuityContextMessage {
|
|
47
|
+
role: "custom";
|
|
48
|
+
customType: typeof COMPACTION_LANGUAGE_MESSAGE;
|
|
49
|
+
content: Array<{ type: "text"; text: string }>;
|
|
50
|
+
display: false;
|
|
51
|
+
details: {
|
|
52
|
+
protocol: typeof COMPACTION_LANGUAGE_PROTOCOL;
|
|
53
|
+
compactionId: string;
|
|
54
|
+
tag: LanguageTag;
|
|
55
|
+
};
|
|
56
|
+
timestamp: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface BranchEntryLike {
|
|
60
|
+
type?: string;
|
|
61
|
+
customType?: string;
|
|
62
|
+
data?: unknown;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const KOREAN_SCRIPT = /[\u3131-\u318e\uac00-\ud7a3]/u;
|
|
66
|
+
const ENGLISH_WORD = /[A-Za-z]+(?:'[A-Za-z]+)?/g;
|
|
67
|
+
const COMMAND_ONLY =
|
|
68
|
+
/^(?:npm|pnpm|yarn|node|npx|git|rg|grep|find|cd|ls|cat|sed|bash)\b(?:\s+[-\w./:@]+)*$/u;
|
|
69
|
+
const WEAK_ENGLISH_INPUTS = new Set([
|
|
70
|
+
"continue",
|
|
71
|
+
"done",
|
|
72
|
+
"go on",
|
|
73
|
+
"next",
|
|
74
|
+
"no",
|
|
75
|
+
"ok",
|
|
76
|
+
"okay",
|
|
77
|
+
"proceed",
|
|
78
|
+
"retry",
|
|
79
|
+
"yes",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
83
|
+
return typeof value === "object" && value !== null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function proseCandidate(text: string): string {
|
|
87
|
+
return text
|
|
88
|
+
.replace(/```[\s\S]*?```/gu, " ")
|
|
89
|
+
.replace(/`[^`\n]*`/gu, " ")
|
|
90
|
+
.replace(/https?:\/\/\S+/gu, " ")
|
|
91
|
+
.replace(/(?:^|\s)(?:\.{0,2}\/|\/)\S+/gu, " ")
|
|
92
|
+
.replace(/\b[\w.-]+\.[A-Za-z0-9]{1,8}\b/gu, " ")
|
|
93
|
+
.replace(/\b(?:route:\S+|[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9_]+)\b/gu, " ")
|
|
94
|
+
.replace(/\s+/gu, " ")
|
|
95
|
+
.trim();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function canonicalLanguageTag(value: string): LanguageTag | undefined {
|
|
99
|
+
try {
|
|
100
|
+
return Intl.getCanonicalLocales(value.trim())[0];
|
|
101
|
+
} catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Conservatively observes direct user prose; weak input preserves prior state. */
|
|
107
|
+
export function detectStrongUserLanguage(
|
|
108
|
+
text: string,
|
|
109
|
+
source: string,
|
|
110
|
+
): LanguageTag | undefined {
|
|
111
|
+
if (source !== "interactive" && source !== "rpc") return undefined;
|
|
112
|
+
const normalized = text.trim();
|
|
113
|
+
if (!normalized) return undefined;
|
|
114
|
+
if (KOREAN_SCRIPT.test(normalized)) return "ko";
|
|
115
|
+
|
|
116
|
+
const lower = normalized.toLowerCase().replace(/\s+/gu, " ");
|
|
117
|
+
if (WEAK_ENGLISH_INPUTS.has(lower) || COMMAND_ONLY.test(normalized)) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const words = proseCandidate(normalized).match(ENGLISH_WORD) ?? [];
|
|
121
|
+
const letterCount = words.reduce((count, word) => count + word.length, 0);
|
|
122
|
+
return words.length >= 2 && letterCount >= 8 ? "en" : undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function languageObserved(tag: LanguageTag): LanguageObserved {
|
|
126
|
+
const canonical = canonicalLanguageTag(tag);
|
|
127
|
+
if (!canonical) throw new Error(`Invalid language tag: ${tag}`);
|
|
128
|
+
return {
|
|
129
|
+
protocol: COMPACTION_LANGUAGE_PROTOCOL,
|
|
130
|
+
kind: "language-observed",
|
|
131
|
+
tag: canonical,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function continuityPending(
|
|
136
|
+
compactionId: string,
|
|
137
|
+
tag: LanguageTag,
|
|
138
|
+
): ContinuityPending {
|
|
139
|
+
const canonical = canonicalLanguageTag(tag);
|
|
140
|
+
if (!canonical) throw new Error(`Invalid language tag: ${tag}`);
|
|
141
|
+
if (!compactionId.trim()) throw new Error("Compaction ID must not be empty.");
|
|
142
|
+
return {
|
|
143
|
+
protocol: COMPACTION_LANGUAGE_PROTOCOL,
|
|
144
|
+
kind: "continuity-pending",
|
|
145
|
+
compactionId,
|
|
146
|
+
tag: canonical,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function continuityConsumed(compactionId: string): ContinuityConsumed {
|
|
151
|
+
if (!compactionId.trim()) throw new Error("Compaction ID must not be empty.");
|
|
152
|
+
return {
|
|
153
|
+
protocol: COMPACTION_LANGUAGE_PROTOCOL,
|
|
154
|
+
kind: "continuity-consumed",
|
|
155
|
+
compactionId,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function normalizeCompactionLanguageEvent(
|
|
160
|
+
value: unknown,
|
|
161
|
+
): CompactionLanguageEvent | undefined {
|
|
162
|
+
if (!isObject(value) || value.protocol !== COMPACTION_LANGUAGE_PROTOCOL) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
if (value.kind === "language-observed" && typeof value.tag === "string") {
|
|
166
|
+
const tag = canonicalLanguageTag(value.tag);
|
|
167
|
+
return tag ? languageObserved(tag) : undefined;
|
|
168
|
+
}
|
|
169
|
+
if (
|
|
170
|
+
value.kind === "continuity-pending" &&
|
|
171
|
+
typeof value.compactionId === "string" &&
|
|
172
|
+
value.compactionId.trim() &&
|
|
173
|
+
typeof value.tag === "string"
|
|
174
|
+
) {
|
|
175
|
+
const tag = canonicalLanguageTag(value.tag);
|
|
176
|
+
return tag ? continuityPending(value.compactionId, tag) : undefined;
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
value.kind === "continuity-consumed" &&
|
|
180
|
+
typeof value.compactionId === "string" &&
|
|
181
|
+
value.compactionId.trim()
|
|
182
|
+
) {
|
|
183
|
+
return continuityConsumed(value.compactionId);
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function initialCompactionLanguageState(): CompactionLanguageState {
|
|
189
|
+
return { consumedCompactionIds: new Set() };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function applyCompactionLanguageEvent(
|
|
193
|
+
state: CompactionLanguageState,
|
|
194
|
+
event: CompactionLanguageEvent,
|
|
195
|
+
): CompactionLanguageState {
|
|
196
|
+
switch (event.kind) {
|
|
197
|
+
case "language-observed":
|
|
198
|
+
return {
|
|
199
|
+
...state,
|
|
200
|
+
language: event.tag,
|
|
201
|
+
pending: state.pending
|
|
202
|
+
? { ...state.pending, tag: event.tag }
|
|
203
|
+
: undefined,
|
|
204
|
+
};
|
|
205
|
+
case "continuity-pending":
|
|
206
|
+
if (
|
|
207
|
+
state.consumedCompactionIds.has(event.compactionId) ||
|
|
208
|
+
state.pending?.compactionId === event.compactionId
|
|
209
|
+
) {
|
|
210
|
+
return state;
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
...state,
|
|
214
|
+
pending: {
|
|
215
|
+
compactionId: event.compactionId,
|
|
216
|
+
tag: event.tag,
|
|
217
|
+
injected: false,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
case "continuity-consumed": {
|
|
221
|
+
if (state.consumedCompactionIds.has(event.compactionId)) return state;
|
|
222
|
+
const consumedCompactionIds = new Set(state.consumedCompactionIds);
|
|
223
|
+
consumedCompactionIds.add(event.compactionId);
|
|
224
|
+
return {
|
|
225
|
+
...state,
|
|
226
|
+
pending:
|
|
227
|
+
state.pending?.compactionId === event.compactionId
|
|
228
|
+
? undefined
|
|
229
|
+
: state.pending,
|
|
230
|
+
consumedCompactionIds,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function markContinuityInjected(
|
|
237
|
+
state: CompactionLanguageState,
|
|
238
|
+
): CompactionLanguageState {
|
|
239
|
+
if (!state.pending || state.pending.injected) return state;
|
|
240
|
+
return { ...state, pending: { ...state.pending, injected: true } };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function settlementContinuityEvent(
|
|
244
|
+
state: CompactionLanguageState,
|
|
245
|
+
): ContinuityConsumed | undefined {
|
|
246
|
+
return state.pending?.injected
|
|
247
|
+
? continuityConsumed(state.pending.compactionId)
|
|
248
|
+
: undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function reconstructCompactionLanguage(
|
|
252
|
+
entries: ReadonlyArray<BranchEntryLike>,
|
|
253
|
+
): CompactionLanguageState {
|
|
254
|
+
return entries.reduce((state, entry) => {
|
|
255
|
+
if (
|
|
256
|
+
entry.type !== "custom" ||
|
|
257
|
+
entry.customType !== COMPACTION_LANGUAGE_ENTRY
|
|
258
|
+
) {
|
|
259
|
+
return state;
|
|
260
|
+
}
|
|
261
|
+
const event = normalizeCompactionLanguageEvent(entry.data);
|
|
262
|
+
return event ? applyCompactionLanguageEvent(state, event) : state;
|
|
263
|
+
}, initialCompactionLanguageState());
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function continuityMarkerText(tag: LanguageTag): string {
|
|
267
|
+
return `Post-compaction continuity: user-visible prose language=${tag}; explicit language requests win.`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function continuityContextMessage(
|
|
271
|
+
pending: PendingContinuity,
|
|
272
|
+
timestamp = Date.now(),
|
|
273
|
+
): ContinuityContextMessage {
|
|
274
|
+
return {
|
|
275
|
+
role: "custom",
|
|
276
|
+
customType: COMPACTION_LANGUAGE_MESSAGE,
|
|
277
|
+
content: [{ type: "text", text: continuityMarkerText(pending.tag) }],
|
|
278
|
+
display: false,
|
|
279
|
+
details: {
|
|
280
|
+
protocol: COMPACTION_LANGUAGE_PROTOCOL,
|
|
281
|
+
compactionId: pending.compactionId,
|
|
282
|
+
tag: pending.tag,
|
|
283
|
+
},
|
|
284
|
+
timestamp,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function projectCompactionContinuity<T>(
|
|
289
|
+
messages: ReadonlyArray<T>,
|
|
290
|
+
state: CompactionLanguageState,
|
|
291
|
+
timestamp = Date.now(),
|
|
292
|
+
):
|
|
293
|
+
| {
|
|
294
|
+
messages: Array<T | ContinuityContextMessage>;
|
|
295
|
+
state: CompactionLanguageState;
|
|
296
|
+
}
|
|
297
|
+
| undefined {
|
|
298
|
+
if (!state.pending) return undefined;
|
|
299
|
+
return {
|
|
300
|
+
messages: [...messages, continuityContextMessage(state.pending, timestamp)],
|
|
301
|
+
state: markContinuityInjected(state),
|
|
302
|
+
};
|
|
303
|
+
}
|
package/extensions/developer.ts
CHANGED
|
@@ -18,6 +18,19 @@ import {
|
|
|
18
18
|
import { Container, Markdown, Text } from "@earendil-works/pi-tui";
|
|
19
19
|
import { Type, type Static } from "typebox";
|
|
20
20
|
|
|
21
|
+
import {
|
|
22
|
+
COMPACTION_LANGUAGE_ENTRY,
|
|
23
|
+
applyCompactionLanguageEvent,
|
|
24
|
+
continuityConsumed,
|
|
25
|
+
continuityPending,
|
|
26
|
+
detectStrongUserLanguage,
|
|
27
|
+
initialCompactionLanguageState,
|
|
28
|
+
languageObserved,
|
|
29
|
+
projectCompactionContinuity,
|
|
30
|
+
reconstructCompactionLanguage,
|
|
31
|
+
settlementContinuityEvent,
|
|
32
|
+
type CompactionLanguageState,
|
|
33
|
+
} from "./compaction-language.ts";
|
|
21
34
|
import {
|
|
22
35
|
availablePackageSkills,
|
|
23
36
|
isWithinRoot,
|
|
@@ -621,6 +634,8 @@ function expandedJudgment(
|
|
|
621
634
|
export default async function developer(pi: ExtensionAPI) {
|
|
622
635
|
let availableSkills = new Map<string, Skill>();
|
|
623
636
|
let state = initialState();
|
|
637
|
+
let compactionLanguage: CompactionLanguageState =
|
|
638
|
+
initialCompactionLanguageState();
|
|
624
639
|
let routeOpening = false;
|
|
625
640
|
const routesWithMutation = new Set<string>();
|
|
626
641
|
let toolPolicyMemory: ToolPolicyMemory = { withheldBuiltins: new Set() };
|
|
@@ -732,9 +747,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
732
747
|
};
|
|
733
748
|
|
|
734
749
|
const reconstruct = (ctx: ExtensionContext) => {
|
|
750
|
+
const branch = ctx.sessionManager.getBranch();
|
|
735
751
|
state = toolPolicyRestartRequired
|
|
736
752
|
? initialState()
|
|
737
|
-
: reconstructState(
|
|
753
|
+
: reconstructState(branch);
|
|
754
|
+
compactionLanguage = reconstructCompactionLanguage(branch);
|
|
738
755
|
syncProtocolTools();
|
|
739
756
|
refreshUI(ctx);
|
|
740
757
|
};
|
|
@@ -752,6 +769,16 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
752
769
|
};
|
|
753
770
|
pi.appendEntry(ACTIVATION_ENTRY, event);
|
|
754
771
|
state = applyDeveloperEvent(state, event);
|
|
772
|
+
if (!enabled && compactionLanguage.pending) {
|
|
773
|
+
const consumed = continuityConsumed(
|
|
774
|
+
compactionLanguage.pending.compactionId,
|
|
775
|
+
);
|
|
776
|
+
pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, consumed);
|
|
777
|
+
compactionLanguage = applyCompactionLanguageEvent(
|
|
778
|
+
compactionLanguage,
|
|
779
|
+
consumed,
|
|
780
|
+
);
|
|
781
|
+
}
|
|
755
782
|
syncProtocolTools();
|
|
756
783
|
refreshUI(ctx);
|
|
757
784
|
return true;
|
|
@@ -2274,6 +2301,41 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
2274
2301
|
},
|
|
2275
2302
|
);
|
|
2276
2303
|
|
|
2304
|
+
pi.on("input", (event) => {
|
|
2305
|
+
if (!state.enabled) return;
|
|
2306
|
+
const tag = detectStrongUserLanguage(event.text, event.source);
|
|
2307
|
+
if (!tag || tag === compactionLanguage.language) return;
|
|
2308
|
+
const observed = languageObserved(tag);
|
|
2309
|
+
pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, observed);
|
|
2310
|
+
compactionLanguage = applyCompactionLanguageEvent(
|
|
2311
|
+
compactionLanguage,
|
|
2312
|
+
observed,
|
|
2313
|
+
);
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
pi.on("session_compact", (event) => {
|
|
2317
|
+
if (!state.enabled || !compactionLanguage.language) return;
|
|
2318
|
+
const pending = continuityPending(
|
|
2319
|
+
event.compactionEntry.id,
|
|
2320
|
+
compactionLanguage.language,
|
|
2321
|
+
);
|
|
2322
|
+
const next = applyCompactionLanguageEvent(compactionLanguage, pending);
|
|
2323
|
+
if (next === compactionLanguage) return;
|
|
2324
|
+
pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, pending);
|
|
2325
|
+
compactionLanguage = next;
|
|
2326
|
+
});
|
|
2327
|
+
|
|
2328
|
+
pi.on("context", (event) => {
|
|
2329
|
+
if (!state.enabled) return;
|
|
2330
|
+
const projection = projectCompactionContinuity(
|
|
2331
|
+
event.messages,
|
|
2332
|
+
compactionLanguage,
|
|
2333
|
+
);
|
|
2334
|
+
if (!projection) return;
|
|
2335
|
+
compactionLanguage = projection.state;
|
|
2336
|
+
return { messages: projection.messages as typeof event.messages };
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2277
2339
|
pi.on("before_agent_start", (event) => {
|
|
2278
2340
|
availableSkills = availablePackageSkills(
|
|
2279
2341
|
event.systemPromptOptions.skills ?? [],
|
|
@@ -2373,7 +2435,17 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
2373
2435
|
if (startEnabled === true && !state.enabled) setEnabled(true, ctx);
|
|
2374
2436
|
});
|
|
2375
2437
|
pi.on("session_tree", (_event, ctx) => reconstruct(ctx));
|
|
2376
|
-
pi.on("agent_settled", (_event, ctx) =>
|
|
2438
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
2439
|
+
const consumed = settlementContinuityEvent(compactionLanguage);
|
|
2440
|
+
if (consumed) {
|
|
2441
|
+
pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, consumed);
|
|
2442
|
+
compactionLanguage = applyCompactionLanguageEvent(
|
|
2443
|
+
compactionLanguage,
|
|
2444
|
+
consumed,
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
refreshUI(ctx);
|
|
2448
|
+
});
|
|
2377
2449
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
2378
2450
|
releaseProtocolTools();
|
|
2379
2451
|
if (!toolPolicyRestartRequired) {
|
package/package.json
CHANGED