instar 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/StateManager.d.ts +1 -0
- package/dist/core/StateManager.js +13 -0
- package/dist/core/types.d.ts +12 -0
- package/dist/publishing/TelegraphService.d.ts +10 -1
- package/dist/publishing/TelegraphService.js +10 -1
- package/dist/scaffold/templates.js +2 -2
- package/dist/scheduler/JobLoader.d.ts +5 -0
- package/dist/scheduler/JobLoader.js +77 -1
- package/dist/scheduler/JobScheduler.js +17 -0
- package/dist/server/routes.js +4 -1
- package/package.json +1 -1
|
@@ -26,6 +26,7 @@ export declare class StateManager {
|
|
|
26
26
|
}): ActivityEvent[];
|
|
27
27
|
get<T>(key: string): T | null;
|
|
28
28
|
set<T>(key: string, value: T): void;
|
|
29
|
+
delete(key: string): boolean;
|
|
29
30
|
/**
|
|
30
31
|
* Write a file atomically — write to .tmp then rename.
|
|
31
32
|
* Prevents corruption from power loss or disk-full mid-write.
|
|
@@ -143,6 +143,19 @@ export class StateManager {
|
|
|
143
143
|
fs.mkdirSync(dir, { recursive: true });
|
|
144
144
|
this.atomicWrite(filePath, JSON.stringify(value, null, 2));
|
|
145
145
|
}
|
|
146
|
+
delete(key) {
|
|
147
|
+
this.validateKey(key, 'state key');
|
|
148
|
+
const filePath = path.join(this.stateDir, 'state', `${key}.json`);
|
|
149
|
+
if (!fs.existsSync(filePath))
|
|
150
|
+
return false;
|
|
151
|
+
try {
|
|
152
|
+
fs.unlinkSync(filePath);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
146
159
|
/**
|
|
147
160
|
* Write a file atomically — write to .tmp then rename.
|
|
148
161
|
* Prevents corruption from power loss or disk-full mid-write.
|
package/dist/core/types.d.ts
CHANGED
|
@@ -61,6 +61,18 @@ export interface JobDefinition {
|
|
|
61
61
|
tags?: string[];
|
|
62
62
|
/** Telegram topic ID this job reports to (auto-created if not set) */
|
|
63
63
|
topicId?: number;
|
|
64
|
+
/** Grounding configuration — what context this job needs at session start */
|
|
65
|
+
grounding?: JobGrounding;
|
|
66
|
+
}
|
|
67
|
+
export interface JobGrounding {
|
|
68
|
+
/** Whether this job requires identity grounding before execution */
|
|
69
|
+
requiresIdentity: boolean;
|
|
70
|
+
/** Whether this job processes external/untrusted input (requires security screening) */
|
|
71
|
+
processesExternalInput?: boolean;
|
|
72
|
+
/** Additional context files to inject at job start (relative to .instar/) */
|
|
73
|
+
contextFiles?: string[];
|
|
74
|
+
/** Custom grounding questions the agent must answer before proceeding */
|
|
75
|
+
questions?: string[];
|
|
64
76
|
}
|
|
65
77
|
export type JobPriority = 'critical' | 'high' | 'medium' | 'low';
|
|
66
78
|
export interface JobExecution {
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* content via the Telegraph API (telegra.ph). Zero-config,
|
|
6
6
|
* no rate limits, instant web pages accessible from anywhere.
|
|
7
7
|
*
|
|
8
|
+
* IMPORTANT: All Telegraph pages are PUBLIC. There is no authentication
|
|
9
|
+
* or access control — anyone with the URL can view the content.
|
|
10
|
+
* Do NOT publish sensitive, private, or confidential information.
|
|
11
|
+
* For private content, use the Cloudflare Tunnel viewer (when configured).
|
|
12
|
+
*
|
|
8
13
|
* Telegraph API docs: https://telegra.ph/api
|
|
9
14
|
*/
|
|
10
15
|
/** Valid Telegraph element tags */
|
|
@@ -79,7 +84,11 @@ export declare class TelegraphService {
|
|
|
79
84
|
*/
|
|
80
85
|
createAccount(shortName: string, authorName?: string): Promise<TelegraphAccount>;
|
|
81
86
|
/**
|
|
82
|
-
* Publish markdown content as a Telegraph page.
|
|
87
|
+
* Publish markdown content as a PUBLIC Telegraph page.
|
|
88
|
+
*
|
|
89
|
+
* WARNING: The published page is publicly accessible to anyone with the URL.
|
|
90
|
+
* Do not publish sensitive or private information.
|
|
91
|
+
*
|
|
83
92
|
* Returns the page URL and path.
|
|
84
93
|
*/
|
|
85
94
|
publishPage(title: string, markdown: string): Promise<TelegraphPage>;
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* content via the Telegraph API (telegra.ph). Zero-config,
|
|
6
6
|
* no rate limits, instant web pages accessible from anywhere.
|
|
7
7
|
*
|
|
8
|
+
* IMPORTANT: All Telegraph pages are PUBLIC. There is no authentication
|
|
9
|
+
* or access control — anyone with the URL can view the content.
|
|
10
|
+
* Do NOT publish sensitive, private, or confidential information.
|
|
11
|
+
* For private content, use the Cloudflare Tunnel viewer (when configured).
|
|
12
|
+
*
|
|
8
13
|
* Telegraph API docs: https://telegra.ph/api
|
|
9
14
|
*/
|
|
10
15
|
import fs from 'node:fs';
|
|
@@ -51,7 +56,11 @@ export class TelegraphService {
|
|
|
51
56
|
}
|
|
52
57
|
// ── Publishing ─────────────────────────────────────────────────
|
|
53
58
|
/**
|
|
54
|
-
* Publish markdown content as a Telegraph page.
|
|
59
|
+
* Publish markdown content as a PUBLIC Telegraph page.
|
|
60
|
+
*
|
|
61
|
+
* WARNING: The published page is publicly accessible to anyone with the URL.
|
|
62
|
+
* Do not publish sensitive or private information.
|
|
63
|
+
*
|
|
55
64
|
* Returns the page URL and path.
|
|
56
65
|
*/
|
|
57
66
|
async publishPage(title, markdown) {
|
|
@@ -170,12 +170,12 @@ This routes feedback to the Instar maintainers automatically. Valid types: \`bug
|
|
|
170
170
|
**Relationships** — Track people I interact with.
|
|
171
171
|
- List: \`curl http://localhost:${port}/relationships\`
|
|
172
172
|
|
|
173
|
-
**Publishing** — Share content as web pages via Telegraph. Instant, zero-config, accessible from anywhere.
|
|
173
|
+
**Publishing** — Share content as PUBLIC web pages via Telegraph. Instant, zero-config, accessible from anywhere.
|
|
174
174
|
- Publish: \`curl -X POST http://localhost:${port}/publish -H 'Content-Type: application/json' -d '{"title":"Page Title","markdown":"# Content here"}'\`
|
|
175
175
|
- List published: \`curl http://localhost:${port}/published\`
|
|
176
176
|
- Edit: \`curl -X PUT http://localhost:${port}/publish/PAGE_PATH -H 'Content-Type: application/json' -d '{"title":"Updated","markdown":"# New content"}'\`
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
**⚠ CRITICAL: All Telegraph pages are PUBLIC.** Anyone with the URL can view the content. There is no authentication or access control. NEVER publish sensitive, private, or confidential information through Telegraph. When sharing a link, always inform the user that the page is publicly accessible.
|
|
179
179
|
|
|
180
180
|
**Scripts** — Reusable capabilities in \`.claude/scripts/\`.
|
|
181
181
|
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Jobs define recurring work the agent should perform:
|
|
5
5
|
* email checks, health probes, content publishing, etc.
|
|
6
|
+
*
|
|
7
|
+
* Grounding-by-default: Jobs are validated for grounding configuration.
|
|
8
|
+
* Missing grounding emits warnings — nudging the practice without breaking
|
|
9
|
+
* existing jobs. Jobs that process external input WITHOUT grounding get
|
|
10
|
+
* louder warnings because they represent a security surface.
|
|
6
11
|
*/
|
|
7
12
|
import type { JobDefinition } from '../core/types.js';
|
|
8
13
|
/**
|
|
@@ -3,11 +3,23 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Jobs define recurring work the agent should perform:
|
|
5
5
|
* email checks, health probes, content publishing, etc.
|
|
6
|
+
*
|
|
7
|
+
* Grounding-by-default: Jobs are validated for grounding configuration.
|
|
8
|
+
* Missing grounding emits warnings — nudging the practice without breaking
|
|
9
|
+
* existing jobs. Jobs that process external input WITHOUT grounding get
|
|
10
|
+
* louder warnings because they represent a security surface.
|
|
6
11
|
*/
|
|
7
12
|
import fs from 'node:fs';
|
|
8
13
|
import { Cron } from 'croner';
|
|
9
14
|
const VALID_PRIORITIES = ['critical', 'high', 'medium', 'low'];
|
|
10
15
|
const VALID_MODELS = ['opus', 'sonnet', 'haiku'];
|
|
16
|
+
/** Slugs for lightweight default jobs where grounding is unnecessary. */
|
|
17
|
+
const GROUNDING_EXEMPT_SLUGS = new Set([
|
|
18
|
+
'health-check',
|
|
19
|
+
'feedback-retry',
|
|
20
|
+
'dispatch-check',
|
|
21
|
+
'update-check',
|
|
22
|
+
]);
|
|
11
23
|
/**
|
|
12
24
|
* Load and validate job definitions from a JSON file.
|
|
13
25
|
* Throws on invalid structure — fail loud at startup, not at runtime.
|
|
@@ -26,10 +38,13 @@ export function loadJobs(jobsFile) {
|
|
|
26
38
|
if (!Array.isArray(raw)) {
|
|
27
39
|
throw new Error(`Jobs file must contain a JSON array, got ${typeof raw}`);
|
|
28
40
|
}
|
|
29
|
-
|
|
41
|
+
const jobs = raw.map((job, index) => {
|
|
30
42
|
validateJob(job, index);
|
|
31
43
|
return job;
|
|
32
44
|
});
|
|
45
|
+
// Grounding-by-default audit — warn about jobs missing grounding config
|
|
46
|
+
auditGrounding(jobs);
|
|
47
|
+
return jobs;
|
|
33
48
|
}
|
|
34
49
|
/**
|
|
35
50
|
* Validate a single job definition.
|
|
@@ -86,5 +101,66 @@ export function validateJob(job, index) {
|
|
|
86
101
|
if (exec.args !== undefined && typeof exec.args !== 'string') {
|
|
87
102
|
throw new Error(`${prefix}: execute.args must be a string if provided, got ${typeof exec.args}`);
|
|
88
103
|
}
|
|
104
|
+
// Grounding config — validate structure if present
|
|
105
|
+
if (j.grounding !== undefined) {
|
|
106
|
+
validateGrounding(j.grounding, prefix);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate grounding configuration structure.
|
|
111
|
+
* Throws on invalid structure — if you're going to declare grounding, do it right.
|
|
112
|
+
*/
|
|
113
|
+
function validateGrounding(grounding, prefix) {
|
|
114
|
+
if (!grounding || typeof grounding !== 'object') {
|
|
115
|
+
throw new Error(`${prefix}: "grounding" must be an object if provided`);
|
|
116
|
+
}
|
|
117
|
+
const g = grounding;
|
|
118
|
+
if (typeof g.requiresIdentity !== 'boolean') {
|
|
119
|
+
throw new Error(`${prefix}: grounding.requiresIdentity must be a boolean`);
|
|
120
|
+
}
|
|
121
|
+
if (g.processesExternalInput !== undefined && typeof g.processesExternalInput !== 'boolean') {
|
|
122
|
+
throw new Error(`${prefix}: grounding.processesExternalInput must be a boolean if provided`);
|
|
123
|
+
}
|
|
124
|
+
if (g.contextFiles !== undefined) {
|
|
125
|
+
if (!Array.isArray(g.contextFiles)) {
|
|
126
|
+
throw new Error(`${prefix}: grounding.contextFiles must be an array of strings if provided`);
|
|
127
|
+
}
|
|
128
|
+
for (const f of g.contextFiles) {
|
|
129
|
+
if (typeof f !== 'string' || !f.trim()) {
|
|
130
|
+
throw new Error(`${prefix}: grounding.contextFiles entries must be non-empty strings`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (g.questions !== undefined) {
|
|
135
|
+
if (!Array.isArray(g.questions)) {
|
|
136
|
+
throw new Error(`${prefix}: grounding.questions must be an array of strings if provided`);
|
|
137
|
+
}
|
|
138
|
+
for (const q of g.questions) {
|
|
139
|
+
if (typeof q !== 'string' || !q.trim()) {
|
|
140
|
+
throw new Error(`${prefix}: grounding.questions entries must be non-empty strings`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Audit loaded jobs for grounding configuration.
|
|
147
|
+
* Emits warnings for jobs missing grounding — the "nudge" layer.
|
|
148
|
+
* Exempt jobs (health-check, dispatch-check, etc.) are skipped silently.
|
|
149
|
+
*/
|
|
150
|
+
function auditGrounding(jobs) {
|
|
151
|
+
const ungrounded = [];
|
|
152
|
+
for (const job of jobs) {
|
|
153
|
+
if (!job.enabled)
|
|
154
|
+
continue;
|
|
155
|
+
if (GROUNDING_EXEMPT_SLUGS.has(job.slug))
|
|
156
|
+
continue;
|
|
157
|
+
if (!job.grounding) {
|
|
158
|
+
ungrounded.push(job.slug);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (ungrounded.length > 0) {
|
|
162
|
+
console.warn(`[JobLoader] Grounding audit: ${ungrounded.length} enabled job(s) lack grounding config: ${ungrounded.join(', ')}. ` +
|
|
163
|
+
`Add a "grounding" field to declare identity and security requirements.`);
|
|
164
|
+
}
|
|
89
165
|
}
|
|
90
166
|
//# sourceMappingURL=JobLoader.js.map
|
|
@@ -212,6 +212,18 @@ export class JobScheduler {
|
|
|
212
212
|
spawnJobSession(job, reason) {
|
|
213
213
|
const prompt = this.buildPrompt(job);
|
|
214
214
|
const sessionName = `job-${job.slug}-${Date.now().toString(36)}`;
|
|
215
|
+
// Write active-job.json BEFORE spawning so the session-start and
|
|
216
|
+
// compaction-recovery hooks can inject job-specific grounding context.
|
|
217
|
+
this.state.set('active-job', {
|
|
218
|
+
slug: job.slug,
|
|
219
|
+
name: job.name,
|
|
220
|
+
description: job.description,
|
|
221
|
+
priority: job.priority,
|
|
222
|
+
sessionName,
|
|
223
|
+
triggeredBy: reason,
|
|
224
|
+
startedAt: new Date().toISOString(),
|
|
225
|
+
grounding: job.grounding ?? null,
|
|
226
|
+
});
|
|
215
227
|
this.sessionManager.spawnSession({
|
|
216
228
|
name: sessionName,
|
|
217
229
|
prompt,
|
|
@@ -285,6 +297,11 @@ export class JobScheduler {
|
|
|
285
297
|
const job = this.jobs.find(j => j.slug === session.jobSlug);
|
|
286
298
|
if (!job)
|
|
287
299
|
return;
|
|
300
|
+
// Clear active-job.json now that the job is done
|
|
301
|
+
const activeJob = this.state.get('active-job');
|
|
302
|
+
if (activeJob?.slug === job.slug) {
|
|
303
|
+
this.state.delete('active-job');
|
|
304
|
+
}
|
|
288
305
|
// Update job state with completion result
|
|
289
306
|
const failed = session.status === 'failed' || session.status === 'killed';
|
|
290
307
|
const existingState = this.state.getJobState(job.slug);
|
package/dist/server/routes.js
CHANGED
|
@@ -820,7 +820,10 @@ export function createRoutes(ctx) {
|
|
|
820
820
|
}
|
|
821
821
|
try {
|
|
822
822
|
const page = await ctx.publisher.publishPage(title, markdown);
|
|
823
|
-
res.status(201).json(
|
|
823
|
+
res.status(201).json({
|
|
824
|
+
...page,
|
|
825
|
+
warning: 'This page is PUBLIC. Anyone with the URL can view it.',
|
|
826
|
+
});
|
|
824
827
|
}
|
|
825
828
|
catch (err) {
|
|
826
829
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|