burhan-mop 0.1.6 → 0.1.8
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/.MOP/PROTOCOL.md +53 -8
- package/.MOP/STATE.json +46 -2
- package/.MOP/scripts/mop-autosycn.mjs +51 -11
- package/.MOP/scripts/mop-core.mjs +183 -31
- package/.agents/AGENTS.md +12 -0
- package/AGENTS.md +15 -3
- package/CLAUDE.md +11 -0
- package/GEMINI.md +8 -0
- package/README.bm.md +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
package/.MOP/PROTOCOL.md
CHANGED
|
@@ -31,7 +31,8 @@ Use this order:
|
|
|
31
31
|
7. Coding/adventure language.
|
|
32
32
|
8. GitHub project link. Required for `team`, optional for `solo`.
|
|
33
33
|
9. GitHub username.
|
|
34
|
-
10. Git commit email.
|
|
34
|
+
10. Git commit email. Default to `github-noreply` so MOP derives the active
|
|
35
|
+
GitHub account email as `ID+USERNAME@users.noreply.github.com`.
|
|
35
36
|
11. If `team`, ask join mode: `open`, `owner-approved`, or `invite`.
|
|
36
37
|
12. Ask auto deploy: `Nak aktifkan auto deploy sekarang? Pilih provider:
|
|
37
38
|
GitHub, Docker, Vercel.` If the user says later/no, answer with the defer
|
|
@@ -40,7 +41,7 @@ Use this order:
|
|
|
40
41
|
After confirmation, run:
|
|
41
42
|
|
|
42
43
|
```bash
|
|
43
|
-
node .MOP/scripts/mop-core.mjs setup --project-name "<name>" --name "<display>" --codename <codename> --password "<password>" --mode <solo|team> --conversation-language "<lang>" --coding-language "<lang>" --git-email
|
|
44
|
+
node .MOP/scripts/mop-core.mjs setup --project-name "<name>" --name "<display>" --codename <codename> --password "<password>" --mode <solo|team> --conversation-language "<lang>" --coding-language "<lang>" --git-email github-noreply [--git-name "<display>"] [--github-username "<github-login>"] [--github-url "<url>"] [--join-mode <mode>]
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
## Agent Naming Ceremony
|
|
@@ -122,6 +123,11 @@ Routing rules:
|
|
|
122
123
|
genuinely requires several areas of expertise.
|
|
123
124
|
- If the route reports `partyMode.active: true`, run Party Mode before the final
|
|
124
125
|
answer. Name any missing participant agents first.
|
|
126
|
+
- If the route reports `nextAction: "name-required-party-agents"`, stop and ask
|
|
127
|
+
every question in `missingAgentQuestions`. Do not continue with the task until
|
|
128
|
+
all required party agents have names.
|
|
129
|
+
- Explicit requests such as `party mode`, `multi-agent`, `semua agent`, or
|
|
130
|
+
`agent bincang` must activate Party Mode automatically.
|
|
125
131
|
- The route JSON includes an `answerContract`. The assistant must restore
|
|
126
132
|
monthly memory, start the visible answer with `answerContract.firstLine`, and
|
|
127
133
|
save a one-line memory after meaningful work.
|
|
@@ -162,6 +168,8 @@ PARTY MODE
|
|
|
162
168
|
- Do not include irrelevant agents just because they exist.
|
|
163
169
|
- If a needed party role has no named agent, ask the user to name that agent
|
|
164
170
|
before using it.
|
|
171
|
+
- If several party agents are missing, ask for all missing names in one reply:
|
|
172
|
+
`Kita ada <N> agent belum ada nama. Beri nama untuk ...`
|
|
165
173
|
|
|
166
174
|
Visible dialogue format:
|
|
167
175
|
|
|
@@ -187,6 +195,32 @@ agent: <from-name> (<from-role>)
|
|
|
187
195
|
<explanation>
|
|
188
196
|
```
|
|
189
197
|
|
|
198
|
+
## Browser And Scraping Gate
|
|
199
|
+
|
|
200
|
+
Browser, scraping, rendered extraction, clicking, login flow, bot-detection, and
|
|
201
|
+
form-filling tasks must pass browser preflight before any browser automation.
|
|
202
|
+
|
|
203
|
+
Run:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
node .MOP/scripts/mop-core.mjs browser preflight
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Rules:
|
|
210
|
+
|
|
211
|
+
- First scan the default browser automatically. On Linux this uses
|
|
212
|
+
`xdg-settings get default-web-browser`; fallback is `$BROWSER`.
|
|
213
|
+
- If Chrome or Chromium is detected, browser automation may proceed with normal
|
|
214
|
+
Chrome-compatible mode.
|
|
215
|
+
- If Edge, Brave, or Opera is detected, use browser-act `chrome-direct` mode and
|
|
216
|
+
guide the user to start that browser with `--remote-debugging-port`.
|
|
217
|
+
- If no supported browser is detected, ask the user which browser they use
|
|
218
|
+
before scraping or browser automation.
|
|
219
|
+
- Do not start a default Chrome session first when the user uses Edge, Brave, or
|
|
220
|
+
Opera.
|
|
221
|
+
- If `agent route` includes `browserPreflight.needsQuestion: true`, ask
|
|
222
|
+
`browserPreflight.question` and stop.
|
|
223
|
+
|
|
190
224
|
## MOP Workflow
|
|
191
225
|
|
|
192
226
|
MOP Workflow is BMAD-inspired and MOP-native. It is the default
|
|
@@ -431,10 +465,13 @@ In team mode, an agent name is the identity.
|
|
|
431
465
|
Autosycn is always available and should be used after meaningful state or file
|
|
432
466
|
changes. It is intentionally identity-safe.
|
|
433
467
|
|
|
434
|
-
Before first push for a member, configure the real
|
|
468
|
+
Before first push for a member, configure the real GitHub identity. Default to
|
|
469
|
+
GitHub noreply so commits attach to the active user account without exposing
|
|
470
|
+
private email:
|
|
435
471
|
|
|
436
472
|
```bash
|
|
437
|
-
|
|
473
|
+
gh auth login
|
|
474
|
+
node .MOP/scripts/mop-core.mjs member git-identity --actor <codename> --name "<display name>" --email github-noreply --github-username "<github-login>"
|
|
438
475
|
```
|
|
439
476
|
|
|
440
477
|
Then run:
|
|
@@ -455,6 +492,13 @@ Autosycn must:
|
|
|
455
492
|
- Commit with `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`, and
|
|
456
493
|
`GIT_COMMITTER_EMAIL` set from member state.
|
|
457
494
|
- Set local `git config user.name` and `user.email` before commit/merge.
|
|
495
|
+
- For member commits, derive `user.email` from the active GitHub CLI account as
|
|
496
|
+
`ID+USERNAME@users.noreply.github.com` when
|
|
497
|
+
`autosync.githubIdentity.useNoreplyForMemberCommits` is enabled.
|
|
498
|
+
- Refuse to commit or push if `gh api user` is authenticated as a different
|
|
499
|
+
GitHub username than the active member's configured `githubUsername`.
|
|
500
|
+
- Use the member GitHub identity for setup, preflight, memory, and work branch
|
|
501
|
+
commits. Use `BURHAN-MOP` only for merge guardian commits.
|
|
458
502
|
- In team mode, `main` is the trunk and each user works on `dev/<codename>`.
|
|
459
503
|
- Push to `dev/<codename>` in team mode and `main` in solo mode.
|
|
460
504
|
- After a team push, BURHAN-MOP reviews `dev/<codename>` and merges it into
|
|
@@ -469,10 +513,11 @@ Autosycn must:
|
|
|
469
513
|
- If `githubUsername` is configured, refuse to push unless `gh api user`
|
|
470
514
|
verifies the same account.
|
|
471
515
|
|
|
472
|
-
Important: GitHub commit attribution comes from commit email.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
516
|
+
Important: GitHub commit attribution comes from commit email. MOP uses the
|
|
517
|
+
active GitHub account noreply email for member commits by default. GitHub push
|
|
518
|
+
actor comes from the credential or SSH key used by `git push`; no script can
|
|
519
|
+
fake that. If GitHub shows the wrong pusher, fix `gh auth login`, Git
|
|
520
|
+
Credential Manager, or the SSH key account.
|
|
476
521
|
|
|
477
522
|
## Default Skill: auto-deploy
|
|
478
523
|
|
package/.MOP/STATE.json
CHANGED
|
@@ -77,7 +77,34 @@
|
|
|
77
77
|
"Normal Party mode should use at least 3 participants and prefer 4 when the task has enough relevant roles.",
|
|
78
78
|
"The primary agent coordinates the party and keeps the user intent visible.",
|
|
79
79
|
"Every visible agent message must use the configured dialogue format.",
|
|
80
|
-
"Agents should ask each other focused questions, answer directly, then hand a clear result to the user."
|
|
80
|
+
"Agents should ask each other focused questions, answer directly, then hand a clear result to the user.",
|
|
81
|
+
"Explicit requests such as party mode, multi-agent, semua agent, or agent discussion must activate Party Mode.",
|
|
82
|
+
"If Party Mode needs unnamed agents, ask for all missing agent names before doing the task."
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
"browserPolicy": {
|
|
86
|
+
"requirePreflightBeforeBrowserWork": true,
|
|
87
|
+
"requireDefaultBrowserCheck": true,
|
|
88
|
+
"supportedChoices": [
|
|
89
|
+
"Chrome",
|
|
90
|
+
"Edge",
|
|
91
|
+
"Brave",
|
|
92
|
+
"Opera"
|
|
93
|
+
],
|
|
94
|
+
"builtinChromeBrowsers": [
|
|
95
|
+
"chrome",
|
|
96
|
+
"chromium"
|
|
97
|
+
],
|
|
98
|
+
"directModeBrowsers": [
|
|
99
|
+
"brave",
|
|
100
|
+
"edge",
|
|
101
|
+
"opera"
|
|
102
|
+
],
|
|
103
|
+
"rules": [
|
|
104
|
+
"Before browser, scraping, extraction, click automation, login flow, or form filling, run browser preflight.",
|
|
105
|
+
"Check the user's default browser automatically when possible.",
|
|
106
|
+
"If the default browser is Edge, Brave, or Opera, use chrome-direct mode and guide the user to start remote debugging.",
|
|
107
|
+
"If no supported browser is detected, ask the user which browser they use before continuing."
|
|
81
108
|
]
|
|
82
109
|
},
|
|
83
110
|
"workflow": {
|
|
@@ -399,7 +426,18 @@
|
|
|
399
426
|
}
|
|
400
427
|
},
|
|
401
428
|
"requireUserGitEmail": true,
|
|
402
|
-
"verifyGhUserWhenConfigured": true
|
|
429
|
+
"verifyGhUserWhenConfigured": true,
|
|
430
|
+
"githubIdentity": {
|
|
431
|
+
"requireMatchedGhUser": true,
|
|
432
|
+
"useNoreplyForMemberCommits": true,
|
|
433
|
+
"noreplyFormat": "id-plus-login",
|
|
434
|
+
"rules": [
|
|
435
|
+
"Member commits must use the active member GitHub account, not an AI or guardian identity.",
|
|
436
|
+
"When gh is authenticated, derive member commit email as ID+USERNAME@users.noreply.github.com.",
|
|
437
|
+
"Refuse autosycn if the active gh account does not match the member githubUsername.",
|
|
438
|
+
"BURHAN-MOP identity is reserved for merge guardian commits only."
|
|
439
|
+
]
|
|
440
|
+
}
|
|
403
441
|
},
|
|
404
442
|
"members": {},
|
|
405
443
|
"installedSkills": [
|
|
@@ -520,6 +558,12 @@
|
|
|
520
558
|
"kind": "operations",
|
|
521
559
|
"purpose": "GitHub repo, branch, PR, issue, release, and merge workflow."
|
|
522
560
|
},
|
|
561
|
+
{
|
|
562
|
+
"role": "browser",
|
|
563
|
+
"title": "Browser Automation Agent",
|
|
564
|
+
"kind": "operations",
|
|
565
|
+
"purpose": "Browser preflight, scraping, rendered extraction, clicking, form filling, login flows, and browser-act coordination."
|
|
566
|
+
},
|
|
523
567
|
{
|
|
524
568
|
"role": "devops",
|
|
525
569
|
"title": "DevOps Engineer",
|
|
@@ -247,22 +247,59 @@ function getMember(state, actor) {
|
|
|
247
247
|
return member;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
function githubIdentityPolicy(state) {
|
|
251
|
+
return state.autosync?.githubIdentity || {
|
|
252
|
+
requireMatchedGhUser: true,
|
|
253
|
+
useNoreplyForMemberCommits: true,
|
|
254
|
+
noreplyFormat: 'id-plus-login'
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function currentGhUser() {
|
|
259
|
+
const result = runOptional('gh', ['api', 'user', '--jq', '{login:.login,id:.id,email:.email}']);
|
|
260
|
+
if (!result.ok) return null;
|
|
261
|
+
try {
|
|
262
|
+
return JSON.parse(result.stdout || '{}');
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function githubNoreplyEmail(user) {
|
|
269
|
+
if (!user?.login || !user?.id) return '';
|
|
270
|
+
return `${user.id}+${user.login}@users.noreply.github.com`;
|
|
271
|
+
}
|
|
272
|
+
|
|
250
273
|
function identityFor(state, actor) {
|
|
251
274
|
const member = getMember(state, actor);
|
|
252
275
|
const identity = member.gitIdentity || {};
|
|
276
|
+
const policy = githubIdentityPolicy(state);
|
|
277
|
+
const gh = currentGhUser();
|
|
253
278
|
const name = identity.name || member.displayName || actor;
|
|
254
|
-
const
|
|
279
|
+
const githubUsername = identity.githubUsername || member.github?.username || gh?.login || '';
|
|
280
|
+
if (gh?.login && githubUsername && policy.requireMatchedGhUser !== false && gh.login.toLowerCase() !== githubUsername.toLowerCase()) {
|
|
281
|
+
throw new Error(`GitHub CLI authenticated as ${gh.login}, expected ${githubUsername}. Refusing to commit or push as the wrong user.`);
|
|
282
|
+
}
|
|
283
|
+
let email = identity.email || member.github?.noreplyEmail || '';
|
|
284
|
+
if (policy.useNoreplyForMemberCommits !== false) {
|
|
285
|
+
email = githubNoreplyEmail(gh);
|
|
286
|
+
if (!email) {
|
|
287
|
+
throw new Error('GitHub noreply identity is required for member commits. Run gh auth login as the real user, or set autosync.githubIdentity.useNoreplyForMemberCommits=false.');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
255
290
|
if (!email && state.autosync?.requireUserGitEmail !== false) {
|
|
256
291
|
throw new Error([
|
|
257
292
|
`Missing git email for ${actor}.`,
|
|
258
|
-
'Set a GitHub-verified email or noreply
|
|
259
|
-
`node .MOP/scripts/mop-core.mjs member git-identity --actor ${actor} --name "${name}" --email
|
|
293
|
+
'Set a GitHub-verified email or let MOP derive GitHub noreply from gh:',
|
|
294
|
+
`node .MOP/scripts/mop-core.mjs member git-identity --actor ${actor} --name "${name}" --email github-noreply [--github-username "<username>"]`
|
|
260
295
|
].join(' '));
|
|
261
296
|
}
|
|
262
297
|
return {
|
|
263
298
|
name,
|
|
264
299
|
email,
|
|
265
|
-
githubUsername
|
|
300
|
+
githubUsername,
|
|
301
|
+
githubUserId: gh?.login?.toLowerCase() === githubUsername.toLowerCase() ? gh.id : identity.githubUserId,
|
|
302
|
+
emailSource: policy.useNoreplyForMemberCommits !== false ? 'github-noreply' : (identity.emailSource || 'manual')
|
|
266
303
|
};
|
|
267
304
|
}
|
|
268
305
|
|
|
@@ -335,15 +372,16 @@ function configureRemote(url, replaceRemote = false) {
|
|
|
335
372
|
}
|
|
336
373
|
|
|
337
374
|
function verifyGhUser(identity, state) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
375
|
+
const policy = githubIdentityPolicy(state);
|
|
376
|
+
if (state.autosync?.verifyGhUserWhenConfigured === false && policy.requireMatchedGhUser === false) return 'skipped';
|
|
377
|
+
const gh = currentGhUser();
|
|
378
|
+
if (!gh?.login) {
|
|
341
379
|
throw new Error('GitHub username is configured, but gh could not verify the active account. Run gh auth login as the real user or set autosync.verifyGhUserWhenConfigured=false for SSH-only workflows.');
|
|
342
380
|
}
|
|
343
|
-
if (gh.
|
|
344
|
-
throw new Error(`GitHub CLI authenticated as ${gh.
|
|
381
|
+
if (identity.githubUsername && gh.login.toLowerCase() !== identity.githubUsername.toLowerCase()) {
|
|
382
|
+
throw new Error(`GitHub CLI authenticated as ${gh.login}, expected ${identity.githubUsername}. Refusing to push as the wrong account.`);
|
|
345
383
|
}
|
|
346
|
-
return `verified:${gh.
|
|
384
|
+
return `verified:${gh.login}:${githubNoreplyEmail(gh) || 'email-unavailable'}`;
|
|
347
385
|
}
|
|
348
386
|
|
|
349
387
|
function runProjectCommand(command, env) {
|
|
@@ -677,6 +715,7 @@ function status() {
|
|
|
677
715
|
workBranchPrefix: state.autosync?.workBranchPrefix || 'dev',
|
|
678
716
|
autoMergeToMain: state.autosync?.autoMergeToMain !== false,
|
|
679
717
|
mergeGuardian: guardianConfig(state),
|
|
718
|
+
githubIdentity: githubIdentityPolicy(state),
|
|
680
719
|
preflightBeforeWork: state.autosync?.preflightBeforeWork !== false,
|
|
681
720
|
requireUserGitEmail: state.autosync?.requireUserGitEmail !== false,
|
|
682
721
|
initialized: state.initialized,
|
|
@@ -691,7 +730,8 @@ function status() {
|
|
|
691
730
|
displayName: member.displayName,
|
|
692
731
|
gitIdentityConfigured: Boolean(member.gitIdentity?.email || member.github?.noreplyEmail),
|
|
693
732
|
gitName: member.gitIdentity?.name || member.displayName || key,
|
|
694
|
-
gitEmail: member.gitIdentity?.email || member.github?.noreplyEmail || ''
|
|
733
|
+
gitEmail: member.gitIdentity?.email || member.github?.noreplyEmail || '',
|
|
734
|
+
githubUsername: member.gitIdentity?.githubUsername || member.github?.username || ''
|
|
695
735
|
}
|
|
696
736
|
]))
|
|
697
737
|
}, null, 2));
|
|
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFile
|
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
6
7
|
|
|
7
8
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const coreDir = resolve(here, '..');
|
|
@@ -71,6 +72,53 @@ function verifyPassword(password, salt, expectedHex) {
|
|
|
71
72
|
return expected.length === actual.length && timingSafeEqual(actual, expected);
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
function currentGhUser() {
|
|
76
|
+
const result = spawnSync('gh', ['api', 'user', '--jq', '{login:.login,id:.id,email:.email}'], {
|
|
77
|
+
cwd: rootDir,
|
|
78
|
+
encoding: 'utf8'
|
|
79
|
+
});
|
|
80
|
+
if (result.status !== 0) return null;
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(result.stdout || '{}');
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function githubNoreplyEmail(user) {
|
|
89
|
+
if (!user?.login || !user?.id) return '';
|
|
90
|
+
return `${user.id}+${user.login}@users.noreply.github.com`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveGitIdentityInput(state, actor, name, emailInput, githubUsernameInput) {
|
|
94
|
+
const policy = state.autosync?.githubIdentity || {};
|
|
95
|
+
const preferNoreply = policy.useNoreplyForMemberCommits !== false;
|
|
96
|
+
const gh = currentGhUser();
|
|
97
|
+
const githubUsername = githubUsernameInput || gh?.login || '';
|
|
98
|
+
if (gh?.login && githubUsername && policy.requireMatchedGhUser !== false && gh.login.toLowerCase() !== githubUsername.toLowerCase()) {
|
|
99
|
+
throw new Error(`GitHub CLI authenticated as ${gh.login}, expected ${githubUsername}. Run gh auth login as the real user.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let email = String(emailInput || '').trim();
|
|
103
|
+
const wantsNoreply = !email || ['auto', 'github', 'github-noreply', 'noreply'].includes(email.toLowerCase());
|
|
104
|
+
if (preferNoreply && wantsNoreply) {
|
|
105
|
+
email = githubNoreplyEmail(gh);
|
|
106
|
+
if (!email) {
|
|
107
|
+
throw new Error('Cannot derive GitHub noreply email. Run gh auth login or provide --git-email "<github-verified-email>".');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!email && state.autosync?.requireUserGitEmail !== false) {
|
|
111
|
+
throw new Error('Git email is required. Use --git-email github-noreply after gh auth login, or provide a GitHub-verified email.');
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
name,
|
|
115
|
+
email,
|
|
116
|
+
githubUsername,
|
|
117
|
+
githubUserId: gh?.login?.toLowerCase() === githubUsername.toLowerCase() ? gh.id : undefined,
|
|
118
|
+
emailSource: wantsNoreply && email ? 'github-noreply' : 'manual'
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
74
122
|
function activeAgentFor(state, actor) {
|
|
75
123
|
const activeId = state.activeAgents?.[actor];
|
|
76
124
|
if (!activeId) return null;
|
|
@@ -184,6 +232,66 @@ function answerContractFor(state, actor, agent = activeAgentFor(state, actor)) {
|
|
|
184
232
|
};
|
|
185
233
|
}
|
|
186
234
|
|
|
235
|
+
function browserPolicy(state) {
|
|
236
|
+
return state.browserPolicy || {
|
|
237
|
+
requirePreflightBeforeBrowserWork: true,
|
|
238
|
+
requireDefaultBrowserCheck: true,
|
|
239
|
+
directModeBrowsers: ['brave', 'edge', 'opera'],
|
|
240
|
+
builtinChromeBrowsers: ['chrome', 'chromium'],
|
|
241
|
+
supportedChoices: ['Chrome', 'Edge', 'Brave', 'Opera']
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function detectDefaultBrowser() {
|
|
246
|
+
const xdg = spawnSync('xdg-settings', ['get', 'default-web-browser'], {
|
|
247
|
+
cwd: rootDir,
|
|
248
|
+
encoding: 'utf8'
|
|
249
|
+
});
|
|
250
|
+
const raw = (xdg.status === 0 ? xdg.stdout : '').trim() || process.env.BROWSER || '';
|
|
251
|
+
const value = raw.toLowerCase();
|
|
252
|
+
let family = 'unknown';
|
|
253
|
+
if (/(google-chrome|chrome)/.test(value) && !/chromium/.test(value)) family = 'chrome';
|
|
254
|
+
else if (/chromium/.test(value)) family = 'chromium';
|
|
255
|
+
else if (/brave/.test(value)) family = 'brave';
|
|
256
|
+
else if (/(microsoft-edge|edge)/.test(value)) family = 'edge';
|
|
257
|
+
else if (/opera/.test(value)) family = 'opera';
|
|
258
|
+
else if (/firefox/.test(value)) family = 'firefox';
|
|
259
|
+
return {
|
|
260
|
+
raw: raw || null,
|
|
261
|
+
family,
|
|
262
|
+
source: raw ? (xdg.status === 0 ? 'xdg-settings' : 'BROWSER') : 'not-detected'
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function browserPreflightFor(state) {
|
|
267
|
+
const policy = browserPolicy(state);
|
|
268
|
+
const detected = detectDefaultBrowser();
|
|
269
|
+
const direct = (policy.directModeBrowsers || []).includes(detected.family);
|
|
270
|
+
const builtin = (policy.builtinChromeBrowsers || []).includes(detected.family);
|
|
271
|
+
const supported = direct || builtin;
|
|
272
|
+
const mode = builtin ? 'chrome' : direct ? 'chrome-direct' : 'ask-user-browser';
|
|
273
|
+
const needsQuestion = !supported;
|
|
274
|
+
return {
|
|
275
|
+
required: policy.requirePreflightBeforeBrowserWork !== false,
|
|
276
|
+
defaultBrowser: detected,
|
|
277
|
+
mode,
|
|
278
|
+
ready: supported,
|
|
279
|
+
needsQuestion,
|
|
280
|
+
question: needsQuestion
|
|
281
|
+
? `Saya tak dapat kesan Chrome/Edge/Brave/Opera sebagai browser default. Awak guna browser apa? Pilih: ${(policy.supportedChoices || ['Chrome', 'Edge', 'Brave', 'Opera']).join(', ')}.`
|
|
282
|
+
: '',
|
|
283
|
+
instructions: direct
|
|
284
|
+
? [
|
|
285
|
+
`Use browser-act chrome-direct for ${detected.family}.`,
|
|
286
|
+
'Guide the user to start that browser with --remote-debugging-port before scraping or form automation.',
|
|
287
|
+
'Do not create a default chrome session first.'
|
|
288
|
+
]
|
|
289
|
+
: builtin
|
|
290
|
+
? ['Use normal Chrome-compatible browser automation.', 'Do not ask the user again unless automation fails.']
|
|
291
|
+
: ['Ask the user which browser they use before scraping or browser automation.']
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
187
295
|
function requireActiveAgent(state, actor, role = 'core', title = 'Core Agent') {
|
|
188
296
|
const agent = activeAgentFor(state, actor);
|
|
189
297
|
if (agent) return agent;
|
|
@@ -335,7 +443,7 @@ const routeRules = [
|
|
|
335
443
|
{
|
|
336
444
|
role: 'browser',
|
|
337
445
|
support: ['researcher', 'tester'],
|
|
338
|
-
keywords: ['browser', 'browse', 'scrape', 'scraping', 'extract', 'click', 'login flow', 'fill form', 'captcha', 'bot detection', 'website', 'url', 'webpage']
|
|
446
|
+
keywords: ['agent browser', 'browser agent', 'browser automation', 'browser', 'browse', 'scrape', 'scraping', 'web scraping', 'extract', 'click', 'login flow', 'fill form', 'captcha', 'bot detection', 'website', 'url', 'webpage']
|
|
339
447
|
},
|
|
340
448
|
{
|
|
341
449
|
role: 'researcher',
|
|
@@ -359,6 +467,10 @@ function routeScore(task, rule) {
|
|
|
359
467
|
return rule.keywords.reduce((score, keyword) => task.includes(keyword) ? score + Math.max(1, keyword.split(/\s+/).length) : score, 0);
|
|
360
468
|
}
|
|
361
469
|
|
|
470
|
+
function hasBrowserWorkIntent(task) {
|
|
471
|
+
return /\b(agent browser|browser agent|browser automation|browse|scrape|scraping|web scraping|extract|click|login flow|fill form|captcha|bot detection|webpage|url)\b/.test(task);
|
|
472
|
+
}
|
|
473
|
+
|
|
362
474
|
function uniqueValues(values) {
|
|
363
475
|
return [...new Set(values.filter(Boolean))];
|
|
364
476
|
}
|
|
@@ -372,6 +484,7 @@ function maybeLimit(values, limit) {
|
|
|
372
484
|
|
|
373
485
|
function shouldActivatePartyMode(state, task, primaryRole, supportRoles, newSystemIntent) {
|
|
374
486
|
if (state.partyMode?.enabled === false || state.partyMode?.autoActivateWhenNeeded === false) return false;
|
|
487
|
+
const explicitPartyIntent = /\b(party mode|party|multi[- ]?agent|swarm|semua agent|banyak agent|agent.*bincang|bincang.*agent|agent.*discuss|discuss.*agent)\b/.test(task);
|
|
375
488
|
const multiDomainIntent = [
|
|
376
489
|
['ui', 'backend'],
|
|
377
490
|
['frontend', 'backend'],
|
|
@@ -381,10 +494,11 @@ function shouldActivatePartyMode(state, task, primaryRole, supportRoles, newSyst
|
|
|
381
494
|
['security', 'auth'],
|
|
382
495
|
['prompt', 'system']
|
|
383
496
|
].some(([first, second]) => task.includes(first) && task.includes(second));
|
|
497
|
+
const browserRiskIntent = hasBrowserWorkIntent(task);
|
|
384
498
|
const connectiveIntent = /\b(connect|connected|integrate|integration|bersambung|sambung|hubung|flow|workflow)\b/.test(task);
|
|
385
499
|
const broadBuild = newSystemIntent && supportRoles.length >= 2;
|
|
386
500
|
const specialistStack = supportRoles.length >= 3 && ['architect', 'planner', 'core'].includes(primaryRole);
|
|
387
|
-
return multiDomainIntent || connectiveIntent || broadBuild || specialistStack;
|
|
501
|
+
return explicitPartyIntent || multiDomainIntent || browserRiskIntent || connectiveIntent || broadBuild || specialistStack;
|
|
388
502
|
}
|
|
389
503
|
|
|
390
504
|
function partyFormat(state) {
|
|
@@ -405,12 +519,14 @@ function partyParticipantsFor(state, primaryRole, supportRoles, scored, partyAct
|
|
|
405
519
|
const minimum = Number(state.partyMode?.minimumParticipants || 3);
|
|
406
520
|
const floor = Number.isFinite(preferredMinimum) ? Math.max(minimum, preferredMinimum) : minimum;
|
|
407
521
|
const fallbackRoles = ['planner', 'researcher', 'reviewer', 'coder', 'architect', 'prompt', 'tester'];
|
|
408
|
-
const
|
|
522
|
+
const relevant = uniqueValues([
|
|
409
523
|
primaryRole,
|
|
410
524
|
...supportRoles,
|
|
411
|
-
...scored.map((rule) => rule.role)
|
|
412
|
-
...fallbackRoles
|
|
525
|
+
...scored.map((rule) => rule.role)
|
|
413
526
|
]);
|
|
527
|
+
const participants = relevant.length >= floor
|
|
528
|
+
? relevant
|
|
529
|
+
: uniqueValues([...relevant, ...fallbackRoles]).slice(0, floor);
|
|
414
530
|
const catalogRoles = new Set((state.agentCatalog || []).map((item) => item.role));
|
|
415
531
|
const filtered = participants.filter((role) => catalogRoles.has(role));
|
|
416
532
|
const enough = filtered.length >= floor ? filtered : participants;
|
|
@@ -428,15 +544,16 @@ function inferAgentRoute(state, taskText) {
|
|
|
428
544
|
const newSystemIntent = /\b(system|sistem|app|tool|platform|website|dashboard|engine|core)\b/.test(task)
|
|
429
545
|
|| /buat sebuah|bina sebuah|create a|build a/.test(task);
|
|
430
546
|
const implementationIntent = /\b(code|coding|implement|fix|ubah file|buat file)\b/.test(task);
|
|
547
|
+
const browserWorkIntent = hasBrowserWorkIntent(task);
|
|
431
548
|
const top = scored[0];
|
|
432
|
-
let primaryRole = top?.role || state.agentPolicy?.defaultRole || 'core';
|
|
549
|
+
let primaryRole = browserWorkIntent ? 'browser' : (top?.role || state.agentPolicy?.defaultRole || 'core');
|
|
433
550
|
|
|
434
|
-
if (newSystemIntent && !implementationIntent && state.agentRouter?.preferHighReasoningForNewSystems !== false) {
|
|
551
|
+
if (newSystemIntent && !implementationIntent && !browserWorkIntent && state.agentRouter?.preferHighReasoningForNewSystems !== false) {
|
|
435
552
|
primaryRole = state.agentRouter?.defaultHighReasoningRole || 'architect';
|
|
436
553
|
}
|
|
437
554
|
|
|
438
555
|
const primary = catalogForRole(state, primaryRole);
|
|
439
|
-
const baseSupport = newSystemIntent
|
|
556
|
+
const baseSupport = newSystemIntent && !browserWorkIntent
|
|
440
557
|
? ['planner', 'researcher', 'prompt', 'coder', 'reviewer']
|
|
441
558
|
: (top?.support || []);
|
|
442
559
|
const supportRoles = maybeLimit(uniqueValues([
|
|
@@ -447,7 +564,7 @@ function inferAgentRoute(state, taskText) {
|
|
|
447
564
|
const partyActive = shouldActivatePartyMode(state, task, primaryRole, supportRoles, newSystemIntent);
|
|
448
565
|
const partyParticipants = partyParticipantsFor(state, primaryRole, supportRoles, scored, partyActive);
|
|
449
566
|
|
|
450
|
-
const ambiguousNewSystem = newSystemIntent && words.length < 18;
|
|
567
|
+
const ambiguousNewSystem = newSystemIntent && !browserWorkIntent && words.length < 18;
|
|
451
568
|
const noClearMatch = scored.length === 0 && words.length > 3;
|
|
452
569
|
const needsClarification = state.agentRouter?.clarifyBeforeActionWhenAmbiguous !== false
|
|
453
570
|
&& (ambiguousNewSystem || noClearMatch || /\b(maybe|mungkin|lebih kurang|macam)\b/.test(task));
|
|
@@ -478,7 +595,7 @@ function inferAgentRoute(state, taskText) {
|
|
|
478
595
|
confidence: top ? Math.min(0.95, 0.45 + (top.score * 0.08)) : 0.35,
|
|
479
596
|
needsClarification,
|
|
480
597
|
questions,
|
|
481
|
-
reason: newSystemIntent
|
|
598
|
+
reason: newSystemIntent && !browserWorkIntent
|
|
482
599
|
? `New or broad system task routed to ${primary.title} for high-reasoning planning before implementation.`
|
|
483
600
|
: top
|
|
484
601
|
? `Matched keywords for ${primary.title}: ${top.keywords.filter((keyword) => task.includes(keyword)).join(', ')}.`
|
|
@@ -502,7 +619,7 @@ function setup(args) {
|
|
|
502
619
|
const codingLanguage = String(args['coding-language'] || 'English');
|
|
503
620
|
const githubUrl = String(args['github-url'] || '');
|
|
504
621
|
const gitName = String(args['git-name'] || displayName);
|
|
505
|
-
const gitEmail = String(args['git-email'] || '');
|
|
622
|
+
const gitEmail = String(args['git-email'] || 'github-noreply');
|
|
506
623
|
const githubUsername = String(args['github-username'] || '');
|
|
507
624
|
const joinMode = String(args['join-mode'] || 'owner-approved');
|
|
508
625
|
|
|
@@ -510,12 +627,10 @@ function setup(args) {
|
|
|
510
627
|
if (password.length < 8) throw new Error('Password must be at least 8 characters.');
|
|
511
628
|
if (!['solo', 'team'].includes(mode)) throw new Error('Mode must be solo or team.');
|
|
512
629
|
if (mode === 'team' && !githubUrl) throw new Error('Team mode requires --github-url.');
|
|
513
|
-
if (state.autosync?.requireUserGitEmail !== false && !gitEmail) {
|
|
514
|
-
throw new Error('Git email is required so commits are attributed to the real user, not the AI tool.');
|
|
515
|
-
}
|
|
516
630
|
if (!['open', 'owner-approved', 'invite'].includes(joinMode)) {
|
|
517
631
|
throw new Error('Join mode must be open, owner-approved, or invite.');
|
|
518
632
|
}
|
|
633
|
+
const gitIdentity = resolveGitIdentityInput(state, codename, gitName, gitEmail, githubUsername);
|
|
519
634
|
|
|
520
635
|
const { passwordHash, passwordSalt } = hashPassword(password);
|
|
521
636
|
state.initialized = true;
|
|
@@ -545,11 +660,7 @@ function setup(args) {
|
|
|
545
660
|
conversation: conversationLanguage,
|
|
546
661
|
coding: codingLanguage
|
|
547
662
|
},
|
|
548
|
-
gitIdentity
|
|
549
|
-
name: gitName,
|
|
550
|
-
email: gitEmail,
|
|
551
|
-
githubUsername
|
|
552
|
-
},
|
|
663
|
+
gitIdentity,
|
|
553
664
|
joinedAt: now()
|
|
554
665
|
}
|
|
555
666
|
};
|
|
@@ -692,13 +803,27 @@ function agentRoute(args) {
|
|
|
692
803
|
};
|
|
693
804
|
}) : [];
|
|
694
805
|
const missingPartyAgents = partyAgents.filter((item) => item.missing);
|
|
806
|
+
const missingAgentCommands = missingPartyAgents.map((item) => (
|
|
807
|
+
`node .MOP/scripts/mop-core.mjs agent activate --actor ${actor} --role ${item.role} --title "${item.title}" --name "<agent-name>"`
|
|
808
|
+
));
|
|
809
|
+
const missingAgentQuestions = missingPartyAgents.map((item) => (
|
|
810
|
+
`Beri nama untuk ${item.title} (${item.role}) kamu:`
|
|
811
|
+
));
|
|
812
|
+
const browserPreflight = route.primaryRole === 'browser'
|
|
813
|
+
|| route.supportRoles.includes('browser')
|
|
814
|
+
|| route.partyMode?.participants?.includes('browser')
|
|
815
|
+
|| hasBrowserWorkIntent(route.task || '')
|
|
816
|
+
? browserPreflightFor(state)
|
|
817
|
+
: null;
|
|
695
818
|
const response = {
|
|
696
819
|
ok: Boolean(agent),
|
|
697
820
|
actor,
|
|
698
821
|
route,
|
|
699
822
|
partyAgents,
|
|
823
|
+
missingAgents: missingPartyAgents,
|
|
700
824
|
activeAgent: null,
|
|
701
825
|
answerContract: null,
|
|
826
|
+
browserPreflight,
|
|
702
827
|
monthlyMemory: {
|
|
703
828
|
restoreCommand: `node .MOP/scripts/mop-core.mjs memory brief --actor ${actor}`,
|
|
704
829
|
saveCommand: `node .MOP/scripts/mop-core.mjs memory add --actor ${actor} --kind conversation --summary "<one-line outcome>"`
|
|
@@ -721,18 +846,33 @@ function agentRoute(args) {
|
|
|
721
846
|
if (route.partyMode.active && missingPartyAgents.length) {
|
|
722
847
|
response.ok = false;
|
|
723
848
|
response.nextAction = 'name-required-party-agents';
|
|
724
|
-
response.message =
|
|
725
|
-
response.
|
|
726
|
-
|
|
727
|
-
|
|
849
|
+
response.message = `Party Mode perlukan ${missingPartyAgents.length} agent yang belum ada nama. Minta nama semua agent ini dahulu sebelum sambung kerja.`;
|
|
850
|
+
response.ask = `Kita ada ${missingPartyAgents.length} agent belum ada nama. ${missingAgentQuestions.join(' ')}`;
|
|
851
|
+
response.missingAgentQuestions = missingAgentQuestions;
|
|
852
|
+
response.missingAgentCommands = missingAgentCommands;
|
|
853
|
+
} else if (browserPreflight?.required && browserPreflight.needsQuestion) {
|
|
854
|
+
response.ok = false;
|
|
855
|
+
response.nextAction = 'ask-browser-before-browser-work';
|
|
856
|
+
response.message = 'Browser preflight belum ready. Tanya browser user dahulu sebelum scraping/browser automation.';
|
|
857
|
+
response.ask = browserPreflight.question;
|
|
728
858
|
} else {
|
|
729
859
|
response.nextAction = route.needsClarification ? 'ask-clarifying-questions' : 'proceed-with-agent';
|
|
730
860
|
}
|
|
731
861
|
} else {
|
|
732
|
-
response.nextAction =
|
|
733
|
-
|
|
734
|
-
|
|
862
|
+
response.nextAction = route.partyMode.active && missingPartyAgents.length
|
|
863
|
+
? 'name-required-party-agents'
|
|
864
|
+
: 'name-required-agent';
|
|
865
|
+
response.message = route.partyMode.active && missingPartyAgents.length
|
|
866
|
+
? `Party Mode perlukan ${missingPartyAgents.length} agent yang belum ada nama. Minta nama semua agent ini dahulu sebelum sambung kerja.`
|
|
867
|
+
: `Task ini perlukan ${route.primaryTitle}. Agent ini belum ada nama lagi atau belum dipilih.`;
|
|
868
|
+
response.ask = route.partyMode.active && missingPartyAgents.length
|
|
869
|
+
? `Kita ada ${missingPartyAgents.length} agent belum ada nama. ${missingAgentQuestions.join(' ')}`
|
|
870
|
+
: `Beri nama untuk ${route.primaryTitle} kamu:`;
|
|
735
871
|
response.command = `node .MOP/scripts/mop-core.mjs agent activate --actor ${actor} --role ${route.primaryRole} --title "${route.primaryTitle}" --name "<agent-name>"`;
|
|
872
|
+
if (route.partyMode.active && missingPartyAgents.length) {
|
|
873
|
+
response.missingAgentQuestions = missingAgentQuestions;
|
|
874
|
+
response.missingAgentCommands = missingAgentCommands;
|
|
875
|
+
}
|
|
736
876
|
if (route.needsClarification) response.afterNaming = route.questions;
|
|
737
877
|
}
|
|
738
878
|
|
|
@@ -747,6 +887,11 @@ function agentList() {
|
|
|
747
887
|
}, null, 2));
|
|
748
888
|
}
|
|
749
889
|
|
|
890
|
+
function browserPreflight() {
|
|
891
|
+
const state = readState();
|
|
892
|
+
console.log(JSON.stringify(browserPreflightFor(state), null, 2));
|
|
893
|
+
}
|
|
894
|
+
|
|
750
895
|
function memoryAdd(args) {
|
|
751
896
|
const state = readState();
|
|
752
897
|
if (!state.initialized) throw new Error('MOP is not initialized.');
|
|
@@ -822,12 +967,12 @@ function memberGitIdentity(args) {
|
|
|
822
967
|
if (!member) throw new Error('Unknown actor.');
|
|
823
968
|
const agent = requireActiveAgent(state, actor);
|
|
824
969
|
const name = String(args.name || member.displayName || actor);
|
|
825
|
-
const email =
|
|
970
|
+
const email = String(args.email || 'github-noreply');
|
|
826
971
|
const githubUsername = String(args['github-username'] || member.gitIdentity?.githubUsername || '');
|
|
827
|
-
member.gitIdentity =
|
|
972
|
+
member.gitIdentity = resolveGitIdentityInput(state, actor, name, email, githubUsername);
|
|
828
973
|
appendLedger(state, actor, 'git-identity', `Updated git identity for ${actor}.`, agent);
|
|
829
974
|
writeState(state);
|
|
830
|
-
console.log(`Git identity set for ${actor}: ${name} <${email}>${githubUsername ? ` github=${githubUsername}` : ''}`);
|
|
975
|
+
console.log(`Git identity set for ${actor}: ${member.gitIdentity.name} <${member.gitIdentity.email}>${member.gitIdentity.githubUsername ? ` github=${member.gitIdentity.githubUsername}` : ''}`);
|
|
831
976
|
}
|
|
832
977
|
|
|
833
978
|
function validate() {
|
|
@@ -841,8 +986,12 @@ function validate() {
|
|
|
841
986
|
if (state.agentPolicy && typeof state.agentPolicy !== 'object') errors.push('agentPolicy must be object');
|
|
842
987
|
if (state.answerPolicy && typeof state.answerPolicy !== 'object') errors.push('answerPolicy must be object');
|
|
843
988
|
if (state.memoryPolicy && typeof state.memoryPolicy !== 'object') errors.push('memoryPolicy must be object');
|
|
989
|
+
if (state.browserPolicy && typeof state.browserPolicy !== 'object') errors.push('browserPolicy must be object');
|
|
844
990
|
if (state.agentRouter && typeof state.agentRouter !== 'object') errors.push('agentRouter must be object');
|
|
845
991
|
if (state.partyMode && typeof state.partyMode !== 'object') errors.push('partyMode must be object');
|
|
992
|
+
if (state.autosync?.githubIdentity && typeof state.autosync.githubIdentity !== 'object') {
|
|
993
|
+
errors.push('autosync.githubIdentity must be object');
|
|
994
|
+
}
|
|
846
995
|
if (state.projectRootPolicy && typeof state.projectRootPolicy !== 'object') errors.push('projectRootPolicy must be object');
|
|
847
996
|
if (state.projectRootPolicy?.rules && !Array.isArray(state.projectRootPolicy.rules)) {
|
|
848
997
|
errors.push('projectRootPolicy.rules must be array');
|
|
@@ -925,6 +1074,7 @@ function status() {
|
|
|
925
1074
|
agentPolicy: state.agentPolicy || {},
|
|
926
1075
|
answerPolicy: answerPolicy(state),
|
|
927
1076
|
memoryPolicy: memoryPolicy(state),
|
|
1077
|
+
browserPolicy: browserPolicy(state),
|
|
928
1078
|
agentRouter: state.agentRouter || {},
|
|
929
1079
|
partyMode: state.partyMode || {},
|
|
930
1080
|
workflow: {
|
|
@@ -978,6 +1128,7 @@ function main() {
|
|
|
978
1128
|
if (command === 'agent' && subcommand === 'require') return agentRequire(args);
|
|
979
1129
|
if (command === 'agent' && subcommand === 'route') return agentRoute(args);
|
|
980
1130
|
if (command === 'agent' && subcommand === 'list') return agentList();
|
|
1131
|
+
if (command === 'browser' && subcommand === 'preflight') return browserPreflight();
|
|
981
1132
|
if (command === 'memory' && subcommand === 'add') return memoryAdd(args);
|
|
982
1133
|
if (command === 'memory' && subcommand === 'brief') return memoryBrief(args);
|
|
983
1134
|
if (command === 'memory' && subcommand === 'restore') return memoryRestore(args);
|
|
@@ -985,15 +1136,16 @@ function main() {
|
|
|
985
1136
|
console.log(`Usage:
|
|
986
1137
|
node .MOP/scripts/mop-core.mjs status
|
|
987
1138
|
node .MOP/scripts/mop-core.mjs validate
|
|
988
|
-
node .MOP/scripts/mop-core.mjs setup --project-name NAME --name DISPLAY --codename CODE --password PASS --mode solo|team --conversation-language LANG --coding-language LANG --git-email EMAIL [--git-name NAME] [--github-username USER] [--github-url URL]
|
|
1139
|
+
node .MOP/scripts/mop-core.mjs setup --project-name NAME --name DISPLAY --codename CODE --password PASS --mode solo|team --conversation-language LANG --coding-language LANG [--git-email github-noreply|EMAIL] [--git-name NAME] [--github-username USER] [--github-url URL]
|
|
989
1140
|
node .MOP/scripts/mop-core.mjs login --codename CODE --password PASS
|
|
990
|
-
node .MOP/scripts/mop-core.mjs member git-identity --actor CODE --name NAME --email EMAIL [--github-username USER]
|
|
1141
|
+
node .MOP/scripts/mop-core.mjs member git-identity --actor CODE --name NAME [--email github-noreply|EMAIL] [--github-username USER]
|
|
991
1142
|
node .MOP/scripts/mop-core.mjs agent activate --actor CODE --role ROLE --title TITLE --name NAME
|
|
992
1143
|
node .MOP/scripts/mop-core.mjs agent use --actor CODE --name NAME
|
|
993
1144
|
node .MOP/scripts/mop-core.mjs agent current --actor CODE
|
|
994
1145
|
node .MOP/scripts/mop-core.mjs agent require --actor CODE [--role ROLE] [--title TITLE]
|
|
995
1146
|
node .MOP/scripts/mop-core.mjs agent route --actor CODE --task "task text"
|
|
996
1147
|
node .MOP/scripts/mop-core.mjs agent list
|
|
1148
|
+
node .MOP/scripts/mop-core.mjs browser preflight
|
|
997
1149
|
node .MOP/scripts/mop-core.mjs memory brief --actor CODE [--month YYYY-MM]
|
|
998
1150
|
node .MOP/scripts/mop-core.mjs memory add --actor CODE --kind conversation --summary "what happened"
|
|
999
1151
|
node .MOP/scripts/mop-core.mjs memory restore --actor CODE`);
|
package/.agents/AGENTS.md
CHANGED
|
@@ -23,6 +23,14 @@ Before doing anything, read `.MOP/STATE.json` and follow
|
|
|
23
23
|
`mop-core.mjs memory add --actor <codename> --kind conversation --summary "<outcome>"`.
|
|
24
24
|
- If the router says clarification is needed, ask the clarifying questions
|
|
25
25
|
before implementation.
|
|
26
|
+
- If the router returns `nextAction: "name-required-party-agents"`, ask every
|
|
27
|
+
question in `missingAgentQuestions` and stop until those agents are named.
|
|
28
|
+
- Before browser, scraping, extraction, click automation, login flow,
|
|
29
|
+
bot-detection, or form-filling work, run `mop-core.mjs browser preflight`. If
|
|
30
|
+
it reports Edge, Brave, or Opera, use browser-act `chrome-direct` mode and
|
|
31
|
+
guide the user to start remote debugging (`--remote-debugging-port`). If it
|
|
32
|
+
cannot detect a supported browser, ask which browser they use before doing
|
|
33
|
+
browser work.
|
|
26
34
|
- If the router activates Party Mode, show visible agent-to-agent dialogue using
|
|
27
35
|
the exact format in `.MOP/PROTOCOL.md`, with `PARTY MODE` shown in
|
|
28
36
|
large uppercase before the dialogue. Party Mode normally uses at least 3
|
|
@@ -34,6 +42,10 @@ Before doing anything, read `.MOP/STATE.json` and follow
|
|
|
34
42
|
- For risky changes, run adversarial review before implementation or merge.
|
|
35
43
|
- When a role agent first appears, ask the user to name it before using it as a
|
|
36
44
|
personal agent.
|
|
45
|
+
- Autosycn member commits must use the active member GitHub account. By default,
|
|
46
|
+
MOP derives `ID+USERNAME@users.noreply.github.com` from `gh api user` and
|
|
47
|
+
refuses mismatched GitHub accounts. `BURHAN-MOP` identity is reserved for
|
|
48
|
+
merge guardian commits only.
|
|
37
49
|
|
|
38
50
|
## Operating Rules
|
|
39
51
|
|
package/AGENTS.md
CHANGED
|
@@ -31,7 +31,16 @@ follow `.MOP/PROTOCOL.md`.
|
|
|
31
31
|
|
|
32
32
|
- If the router marks the task as ambiguous, the named primary agent must ask
|
|
33
33
|
clarifying questions before implementation.
|
|
34
|
-
-
|
|
34
|
+
- If the router returns `nextAction: "name-required-party-agents"`, ask every
|
|
35
|
+
question in `missingAgentQuestions` and stop until those agents are named.
|
|
36
|
+
- **[browser] Agent Rule**: Before browser, scraping, extraction, click
|
|
37
|
+
automation, login flow, bot-detection, or form-filling work, run
|
|
38
|
+
`mop-core.mjs browser preflight`. It scans the default browser when possible.
|
|
39
|
+
If it reports Edge, Brave, or Opera, use browser-act `chrome-direct` mode and
|
|
40
|
+
guide the user to start remote debugging (`--remote-debugging-port`). If it
|
|
41
|
+
cannot detect a supported browser, ask which browser they use before doing
|
|
42
|
+
browser work. Never create a default Chrome session first when the user uses
|
|
43
|
+
another supported browser.
|
|
35
44
|
- If the router returns `partyMode.active: true`, use Party Mode. Show
|
|
36
45
|
`PARTY MODE` in large uppercase before the dialogue, then generate the
|
|
37
46
|
agent-to-agent and agent-to-user dialogue explicitly in your response using this exact format:
|
|
@@ -46,8 +55,8 @@ follow `.MOP/PROTOCOL.md`.
|
|
|
46
55
|
|
|
47
56
|
For `/mop-setup`, ask in order: project name (default folder name), owner display
|
|
48
57
|
name, owner codename, password, solo/team, conversation language, coding
|
|
49
|
-
language, GitHub link, GitHub username, Git commit email
|
|
50
|
-
then whether to activate auto-deploy now or later.
|
|
58
|
+
language, GitHub link, GitHub username, Git commit email (`github-noreply` by
|
|
59
|
+
default), join mode if team, then whether to activate auto-deploy now or later.
|
|
51
60
|
|
|
52
61
|
Agent role/template names are not personal AI names. When a role agent is first
|
|
53
62
|
needed, ask the user to name it and save it with `mop-core.mjs agent activate`.
|
|
@@ -62,6 +71,9 @@ agent route instead of answering invisibly.
|
|
|
62
71
|
Default skill: `autosycn`. After meaningful changes, use
|
|
63
72
|
`.MOP/scripts/mop-autosycn.mjs`. It must commit and merge with the
|
|
64
73
|
real member Git identity from state, never with the AI tool identity.
|
|
74
|
+
Member commits must use the active member GitHub account; by default MOP derives
|
|
75
|
+
`ID+USERNAME@users.noreply.github.com` from `gh api user` and refuses mismatched
|
|
76
|
+
GitHub accounts. `BURHAN-MOP` identity is only for merge guardian commits.
|
|
65
77
|
In team mode, run `preflight --actor <codename>` before starting work; work goes
|
|
66
78
|
to `dev/<codename>`. Every small or large change is pushed there first, then
|
|
67
79
|
BURHAN-MOP reviews and merges to `main` only when checks pass.
|
package/CLAUDE.md
CHANGED
|
@@ -23,6 +23,14 @@ follow `.MOP/PROTOCOL.md`.
|
|
|
23
23
|
`mop-core.mjs memory add --actor <codename> --kind conversation --summary "<one-line outcome>"`.
|
|
24
24
|
- If the router marks the task as ambiguous, the named primary agent must ask
|
|
25
25
|
clarifying questions before implementation.
|
|
26
|
+
- If the router returns `nextAction: "name-required-party-agents"`, ask every
|
|
27
|
+
question in `missingAgentQuestions` and stop until those agents are named.
|
|
28
|
+
- Before browser, scraping, extraction, click automation, login flow,
|
|
29
|
+
bot-detection, or form-filling work, run `mop-core.mjs browser preflight`. If
|
|
30
|
+
it reports Edge, Brave, or Opera, use browser-act `chrome-direct` mode and
|
|
31
|
+
guide the user to start remote debugging (`--remote-debugging-port`). If it
|
|
32
|
+
cannot detect a supported browser, ask which browser they use before doing
|
|
33
|
+
browser work.
|
|
26
34
|
- If the router returns `partyMode.active: true`, use Party Mode. Show
|
|
27
35
|
`PARTY MODE` in large uppercase before the dialogue, then show
|
|
28
36
|
agent-to-agent and agent-to-user dialogue with the exact format from
|
|
@@ -47,6 +55,9 @@ agent route instead of answering invisibly.
|
|
|
47
55
|
Default skill: `autosycn`. After meaningful changes, use
|
|
48
56
|
`.MOP/scripts/mop-autosycn.mjs`. It must commit and merge with the
|
|
49
57
|
real member Git identity from state, never with the AI tool identity.
|
|
58
|
+
Member commits must use the active member GitHub account; by default MOP derives
|
|
59
|
+
`ID+USERNAME@users.noreply.github.com` from `gh api user` and refuses mismatched
|
|
60
|
+
GitHub accounts. `BURHAN-MOP` identity is only for merge guardian commits.
|
|
50
61
|
In team mode, run `preflight --actor <codename>` before starting work; work goes
|
|
51
62
|
to `dev/<codename>`. Every small or large change is pushed there first, then
|
|
52
63
|
BURHAN-MOP reviews and merges to `main` only when checks pass.
|
package/GEMINI.md
CHANGED
|
@@ -16,6 +16,14 @@ rules are imported below.
|
|
|
16
16
|
example: `agent: <agent-name> (<agent-role>) to <user>`.
|
|
17
17
|
- Save a one-line memory after meaningful work with
|
|
18
18
|
`mop-core.mjs memory add --actor <codename> --kind conversation --summary "<outcome>"`.
|
|
19
|
+
- If `agent route` returns `nextAction: "name-required-party-agents"`, ask every
|
|
20
|
+
question in `missingAgentQuestions` and stop until those agents are named.
|
|
21
|
+
- Before browser, scraping, extraction, click automation, login flow,
|
|
22
|
+
bot-detection, or form-filling work, run `mop-core.mjs browser preflight` and
|
|
23
|
+
follow its mode/question before doing browser work.
|
|
24
|
+
- Autosycn member commits must use the active member GitHub account. MOP derives
|
|
25
|
+
`ID+USERNAME@users.noreply.github.com` from `gh api user` by default and
|
|
26
|
+
refuses mismatched GitHub accounts.
|
|
19
27
|
- Treat the current workspace root as the project root; do not create a nested
|
|
20
28
|
project wrapper folder for app work unless the user explicitly asks for it.
|
|
21
29
|
- Use `.gemini/settings.json` for context filenames and MCP server setup.
|
package/README.bm.md
CHANGED
|
@@ -142,7 +142,7 @@ dan merge hanya bila workflow selamat.
|
|
|
142
142
|
| --- | --- |
|
|
143
143
|
| npm package | [`burhan-mop`](https://www.npmjs.com/package/burhan-mop) |
|
|
144
144
|
| command latest | `npx burhan-mop install` |
|
|
145
|
-
| GitHub release | [`v0.1.
|
|
145
|
+
| GitHub release | [`v0.1.8`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.8) |
|
|
146
146
|
| Node | `>=20` |
|
|
147
147
|
|
|
148
148
|
## Links
|
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ and merges only when the workflow is safe.
|
|
|
141
141
|
| --- | --- |
|
|
142
142
|
| npm package | [`burhan-mop`](https://www.npmjs.com/package/burhan-mop) |
|
|
143
143
|
| latest command | `npx burhan-mop install` |
|
|
144
|
-
| GitHub release | [`v0.1.
|
|
144
|
+
| GitHub release | [`v0.1.8`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.8) |
|
|
145
145
|
| Node | `>=20` |
|
|
146
146
|
|
|
147
147
|
## Links
|